'movement' -> 'legacy' to signal things we still need to bring in

This commit is contained in:
Joey Castillo
2024-11-27 10:56:50 -05:00
parent bc08c5a05e
commit 9719567047
221 changed files with 6 additions and 6 deletions

43
legacy/alt_fw/alt_time.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
beats_face,
day_one_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

45
legacy/alt_fw/backer.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
world_clock_face,
sunrise_sunset_face,
moon_phase_face,
temperature_display_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

View File

@@ -0,0 +1,63 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
// Preset Goldstone (GO), Madrid (MA), and Canberra (CA) time zones.
// Also prepopulate the Day One register with Voyager 1's launch (September 5, 1977)
#define MOVEMENT_CUSTOM_BOOT_COMMANDS() { \
/* Standard Time */\
watch_store_backup_data(0x1e0c0c, 4);\
watch_store_backup_data(0x010115, 5);\
watch_store_backup_data(0x130105, 6);\
/* Daylight Saving Time */\
/*\
watch_store_backup_data(0x1f0c0c, 4);\
watch_store_backup_data(0x020115, 5);\
watch_store_backup_data(0x110105, 6);\
*/\
watch_store_backup_data(0x0597b9, 2);\
}
const watch_face_t watch_faces[] = {
simple_clock_face,
mars_time_face,
world_clock_face,
world_clock_face,
world_clock_face,
day_one_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

45
legacy/alt_fw/focus.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
tomato_face,
stopwatch_face,
countdown_face,
wake_face, // added by @joshber 2022-07-23, per @joeycastillo
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

View File

@@ -0,0 +1,45 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
stopwatch_face,
countdown_face,
counter_face,
pulsometer_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

View File

@@ -0,0 +1,46 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
sunrise_sunset_face,
moon_phase_face,
temperature_display_face,
temperature_logging_face,
blinky_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

View File

@@ -0,0 +1,47 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
#define MOVEMENT_DEFAULT_RED_COLOR 0xF
#define MOVEMENT_DEFAULT_GREEN_COLOR 0x0
const watch_face_t watch_faces[] = {
simple_clock_face,
astronomy_face,
sunrise_sunset_face,
moon_phase_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

45
legacy/alt_fw/timers.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* MIT License
*
* 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 MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_
#include "movement_faces.h"
const watch_face_t watch_faces[] = {
simple_clock_face,
wake_face,
interval_face,
stopwatch_face,
sunrise_sunset_face,
preferences_face,
set_time_face,
};
#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))
#define SIGNAL_TUNE_DEFAULT
#endif // MOVEMENT_CONFIG_H_

21
legacy/lib/TOTP/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Weravech
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.

68
legacy/lib/TOTP/README.md Normal file
View File

@@ -0,0 +1,68 @@
TOTP Pure C Library
====================
Library to generate Time-based One-Time Passwords.
Implements the Time-based One-Time Password algorithm specified in [RFC 6238](https://tools.ietf.org/html/rfc6238).
Supports different time steps and is compatible with tokens that use the same standard (including software ones, like the Google Authenticator app).
The code is made of :
- [TOTP-MCU](https://github.com/Netthaw/TOTP-MCU) for `TimeStruct2Timestamp`, `getCodeFromTimestamp`, `getCodeFromTimeStruct`, part of `getCodeFromSteps` and `TOTP_HMAC_SHA*` functions
- [mbedtls](https://github.com/Mbed-TLS/mbedtls) for SHA1/SHA224/SHA256/SHA384/SHA512 implementations
- [this project](https://github.com/mygityf/cipher/blob/master/cipher/hmac.c) as an inspiration for writing the code to compute the TOTP using the key and the text to hash
Supported algorithms are SHA1/SHA224/SHA256/SHA384/SHA512.
Installation & usage:
--------------------
First include header to your file
```c
#include "TOTP.h"
```
After included, define key ex. Key is ```MyLegoDoor```
- Note: The format of hmacKey is array of hexadecimal bytes.
- Most websites provide the key encoded in base32 - RFC3548/RFC4648, either upper or lower case. You can use [this site](https://cryptii.com/pipes/base32-to-hex) to convert the base32 string to hex (make sure you upcase it first if it's lowercase and remove all whitespaces).
```c
uint8_t hmacKey[] = {0x4d, 0x79, 0x4c, 0x65, 0x67, 0x6f, 0x44, 0x6f, 0x6f, 0x72}; // Secret key
```
Instantiate the TOTP class by providing the secret hmacKey, the length of the hmacKey, the Timestep between codes and the algorithm used (most of the time, `SHA1`).
```c
TOTP(hmacKey, 10, 30, SHA1); // Secret key, Secret key length, Timestep (30s), Algorithm
```
Use the ```getCodeFromTimestamp()``` function to get a TOTP from a unix epoch timestamp
```c
uint32_t newCode = getCodeFromTimestamp(1557414000); // Current timestamp since Unix epoch in seconds
```
Or ```getCodeFromTimeStruct()``` if you want to get a TOTP from a tm struct (Time Struct in C),
```c
struct tm datetime;
datetime.tm_hour = 9;
datetime.tm_min = 0;
datetime.tm_sec = 0;
datetime.tm_mday = 13;
datetime.tm_mon = 5;
datetime.tm_year = 2019;
uint32_t newCode = getCodeFromTimeStruct(datetime);
```
If the provided unix timestamp isn't in UTC±0, use ```setTimezone()``` before ```getCodeFromTimestamp()``` or ```getCodeFromTimeStruct()``` to offset the time.
```c
setTimezone(9); // Set timezone +9 Japan
```
You can see an example in example.c (compile it with `gcc -o example example.c sha1.c sha256.c sha512.c TOTP.c -I.`)
Thanks to:
----------
* Netthaw, https://github.com/Netthaw/TOTP-MCU
* Mbed-TLS, https://github.com/Mbed-TLS/mbedtls
* mygityf, https://github.com/mygityf/cipher/blob/master/cipher/hmac.c
* susam, https://github.com/susam/mintotp

69
legacy/lib/TOTP/TOTP.c Normal file
View File

@@ -0,0 +1,69 @@
#include "TOTP.h"
#include "sha1.h"
#include "sha256.h"
#include "sha512.h"
#include <stdio.h>
uint8_t* _hmacKey;
uint8_t _keyLength;
uint8_t _timeZoneOffset;
uint32_t _timeStep;
hmac_alg _algorithm;
// Init the library with the private key, its length, the timeStep duration and the algorithm that should be used
void TOTP(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStep, hmac_alg algorithm) {
_hmacKey = hmacKey;
_keyLength = keyLength;
_timeStep = timeStep;
_algorithm = algorithm;
}
void setTimezone(uint8_t timezone){
_timeZoneOffset = timezone;
}
static uint32_t TimeStruct2Timestamp(struct tm time){
//time.tm_mon -= 1;
//time.tm_year -= 1900;
return mktime(&(time)) - (_timeZoneOffset * 3600) - 2208988800;
}
// Generate a code, using the timestamp provided
uint32_t getCodeFromTimestamp(uint32_t timeStamp) {
uint32_t steps = timeStamp / _timeStep;
return getCodeFromSteps(steps);
}
// Generate a code, using the timestamp provided
uint32_t getCodeFromTimeStruct(struct tm time) {
return getCodeFromTimestamp(TimeStruct2Timestamp(time));
}
// Generate a code, using the number of steps provided
uint32_t getCodeFromSteps(uint32_t steps) {
// STEP 0, map the number of steps in a 8-bytes array (counter value)
uint8_t _byteArray[8];
_byteArray[0] = 0x00;
_byteArray[1] = 0x00;
_byteArray[2] = 0x00;
_byteArray[3] = 0x00;
_byteArray[4] = (uint8_t)((steps >> 24) & 0xFF);
_byteArray[5] = (uint8_t)((steps >> 16) & 0xFF);
_byteArray[6] = (uint8_t)((steps >> 8) & 0XFF);
_byteArray[7] = (uint8_t)((steps & 0XFF));
switch(_algorithm){
case SHA1:
return(TOTP_HMAC_SHA1(_hmacKey, _keyLength, _byteArray, 8));
case SHA224:
return(TOTP_HMAC_SHA256(_hmacKey, _keyLength, _byteArray, 8, 1));
case SHA256:
return(TOTP_HMAC_SHA256(_hmacKey, _keyLength, _byteArray, 8, 0));
case SHA384:
return(TOTP_HMAC_SHA512(_hmacKey, _keyLength, _byteArray, 8, 1));
case SHA512:
return(TOTP_HMAC_SHA512(_hmacKey, _keyLength, _byteArray, 8, 0));
default:
return(0);
}
}

21
legacy/lib/TOTP/TOTP.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef TOTP_H_
#define TOTP_H_
#include <inttypes.h>
#include "time.h"
typedef enum __attribute__ ((__packed__)) {
SHA1,
SHA224,
SHA256,
SHA384,
SHA512
} hmac_alg;
void TOTP(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStep, hmac_alg algorithm);
void setTimezone(uint8_t timezone);
uint32_t getCodeFromTimestamp(uint32_t timeStamp);
uint32_t getCodeFromTimeStruct(struct tm time);
uint32_t getCodeFromSteps(uint32_t steps);
#endif // TOTP_H_

27
legacy/lib/TOTP/example.c Normal file
View File

@@ -0,0 +1,27 @@
#include "TOTP.h"
#include <stdio.h>
/**
* example.c
*/
void main(void)
{
uint8_t hmacKey[] = {0x4d, 0x79, 0x4c, 0x65, 0x67, 0x6f, 0x44, 0x6f, 0x6f, 0x72}; // Secret key
TOTP(hmacKey, 10, 7200, SHA1); // Secret key, Key length, Timestep (7200s - 2hours)
setTimezone(9); // Set timezone
uint32_t newCode = getCodeFromTimestamp(1557414000); // Timestamp Now
///////////////// For struct tm //////////////////
// struct tm datetime;
// datetime.tm_hour = 9;
// datetime.tm_min = 0;
// datetime.tm_sec = 0;
// datetime.tm_mday = 13;
// datetime.tm_mon = 5;
// datetime.tm_year = 2019;
// uint32_t newCode = getCodeFromTimeStruct(datetime);
///////////////////////////////////////////////////
printf("Code : %06u\n",newCode);
}

398
legacy/lib/TOTP/sha1.c Normal file
View File

@@ -0,0 +1,398 @@
/*
* FIPS-180-1 compliant SHA-1 implementation
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
/*
* The SHA-1 standard was published by NIST in 1993.
*
* http://www.itl.nist.gov/fipspubs/fip180-1.htm
*/
#include "sha1.h"
#include <string.h>
#include <stdio.h>
/* Implementation that should never be optimized out by the compiler */
static void mbedtls_zeroize( void *v, size_t n ) {
volatile unsigned char *p = v; while( n-- ) *p++ = 0;
}
/*
* 32-bit integer manipulation macros (big endian)
*/
#ifndef GET_UINT32_BE
#define GET_UINT32_BE(n,b,i) \
{ \
(n) = ( (uint32_t) (b)[(i) ] << 24 ) \
| ( (uint32_t) (b)[(i) + 1] << 16 ) \
| ( (uint32_t) (b)[(i) + 2] << 8 ) \
| ( (uint32_t) (b)[(i) + 3] ); \
}
#endif
#ifndef PUT_UINT32_BE
#define PUT_UINT32_BE(n,b,i) \
{ \
(b)[(i) ] = (unsigned char) ( (n) >> 24 ); \
(b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \
(b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \
(b)[(i) + 3] = (unsigned char) ( (n) ); \
}
#endif
void mbedtls_sha1_init( mbedtls_sha1_context *ctx )
{
memset( ctx, 0, sizeof( mbedtls_sha1_context ) );
}
void mbedtls_sha1_free( mbedtls_sha1_context *ctx )
{
if( ctx == NULL )
return;
mbedtls_zeroize( ctx, sizeof( mbedtls_sha1_context ) );
}
/*
* SHA-1 context setup
*/
void mbedtls_sha1_starts( mbedtls_sha1_context *ctx )
{
ctx->total[0] = 0;
ctx->total[1] = 0;
ctx->state[0] = 0x67452301;
ctx->state[1] = 0xEFCDAB89;
ctx->state[2] = 0x98BADCFE;
ctx->state[3] = 0x10325476;
ctx->state[4] = 0xC3D2E1F0;
}
void mbedtls_sha1_process( mbedtls_sha1_context *ctx, const unsigned char data[SHA1_BLOCK_LENGTH] )
{
uint32_t temp, W[16], A, B, C, D, E;
GET_UINT32_BE( W[ 0], data, 0 );
GET_UINT32_BE( W[ 1], data, 4 );
GET_UINT32_BE( W[ 2], data, 8 );
GET_UINT32_BE( W[ 3], data, 12 );
GET_UINT32_BE( W[ 4], data, 16 );
GET_UINT32_BE( W[ 5], data, 20 );
GET_UINT32_BE( W[ 6], data, 24 );
GET_UINT32_BE( W[ 7], data, 28 );
GET_UINT32_BE( W[ 8], data, 32 );
GET_UINT32_BE( W[ 9], data, 36 );
GET_UINT32_BE( W[10], data, 40 );
GET_UINT32_BE( W[11], data, 44 );
GET_UINT32_BE( W[12], data, 48 );
GET_UINT32_BE( W[13], data, 52 );
GET_UINT32_BE( W[14], data, 56 );
GET_UINT32_BE( W[15], data, 60 );
#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
#define R(t) \
( \
temp = W[( t - 3 ) & 0x0F] ^ W[( t - 8 ) & 0x0F] ^ \
W[( t - 14 ) & 0x0F] ^ W[ t & 0x0F], \
( W[t & 0x0F] = S(temp,1) ) \
)
#define P(a,b,c,d,e,x) \
{ \
e += S(a,5) + F(b,c,d) + K + x; b = S(b,30); \
}
A = ctx->state[0];
B = ctx->state[1];
C = ctx->state[2];
D = ctx->state[3];
E = ctx->state[4];
#define F(x,y,z) (z ^ (x & (y ^ z)))
#define K 0x5A827999
P( A, B, C, D, E, W[0] );
P( E, A, B, C, D, W[1] );
P( D, E, A, B, C, W[2] );
P( C, D, E, A, B, W[3] );
P( B, C, D, E, A, W[4] );
P( A, B, C, D, E, W[5] );
P( E, A, B, C, D, W[6] );
P( D, E, A, B, C, W[7] );
P( C, D, E, A, B, W[8] );
P( B, C, D, E, A, W[9] );
P( A, B, C, D, E, W[10] );
P( E, A, B, C, D, W[11] );
P( D, E, A, B, C, W[12] );
P( C, D, E, A, B, W[13] );
P( B, C, D, E, A, W[14] );
P( A, B, C, D, E, W[15] );
P( E, A, B, C, D, R(16) );
P( D, E, A, B, C, R(17) );
P( C, D, E, A, B, R(18) );
P( B, C, D, E, A, R(19) );
#undef K
#undef F
#define F(x,y,z) (x ^ y ^ z)
#define K 0x6ED9EBA1
P( A, B, C, D, E, R(20) );
P( E, A, B, C, D, R(21) );
P( D, E, A, B, C, R(22) );
P( C, D, E, A, B, R(23) );
P( B, C, D, E, A, R(24) );
P( A, B, C, D, E, R(25) );
P( E, A, B, C, D, R(26) );
P( D, E, A, B, C, R(27) );
P( C, D, E, A, B, R(28) );
P( B, C, D, E, A, R(29) );
P( A, B, C, D, E, R(30) );
P( E, A, B, C, D, R(31) );
P( D, E, A, B, C, R(32) );
P( C, D, E, A, B, R(33) );
P( B, C, D, E, A, R(34) );
P( A, B, C, D, E, R(35) );
P( E, A, B, C, D, R(36) );
P( D, E, A, B, C, R(37) );
P( C, D, E, A, B, R(38) );
P( B, C, D, E, A, R(39) );
#undef K
#undef F
#define F(x,y,z) ((x & y) | (z & (x | y)))
#define K 0x8F1BBCDC
P( A, B, C, D, E, R(40) );
P( E, A, B, C, D, R(41) );
P( D, E, A, B, C, R(42) );
P( C, D, E, A, B, R(43) );
P( B, C, D, E, A, R(44) );
P( A, B, C, D, E, R(45) );
P( E, A, B, C, D, R(46) );
P( D, E, A, B, C, R(47) );
P( C, D, E, A, B, R(48) );
P( B, C, D, E, A, R(49) );
P( A, B, C, D, E, R(50) );
P( E, A, B, C, D, R(51) );
P( D, E, A, B, C, R(52) );
P( C, D, E, A, B, R(53) );
P( B, C, D, E, A, R(54) );
P( A, B, C, D, E, R(55) );
P( E, A, B, C, D, R(56) );
P( D, E, A, B, C, R(57) );
P( C, D, E, A, B, R(58) );
P( B, C, D, E, A, R(59) );
#undef K
#undef F
#define F(x,y,z) (x ^ y ^ z)
#define K 0xCA62C1D6
P( A, B, C, D, E, R(60) );
P( E, A, B, C, D, R(61) );
P( D, E, A, B, C, R(62) );
P( C, D, E, A, B, R(63) );
P( B, C, D, E, A, R(64) );
P( A, B, C, D, E, R(65) );
P( E, A, B, C, D, R(66) );
P( D, E, A, B, C, R(67) );
P( C, D, E, A, B, R(68) );
P( B, C, D, E, A, R(69) );
P( A, B, C, D, E, R(70) );
P( E, A, B, C, D, R(71) );
P( D, E, A, B, C, R(72) );
P( C, D, E, A, B, R(73) );
P( B, C, D, E, A, R(74) );
P( A, B, C, D, E, R(75) );
P( E, A, B, C, D, R(76) );
P( D, E, A, B, C, R(77) );
P( C, D, E, A, B, R(78) );
P( B, C, D, E, A, R(79) );
#undef K
#undef F
ctx->state[0] += A;
ctx->state[1] += B;
ctx->state[2] += C;
ctx->state[3] += D;
ctx->state[4] += E;
}
/*
* SHA-1 process buffer
*/
void mbedtls_sha1_update( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen )
{
size_t fill;
uint32_t left;
if( ilen == 0 )
return;
left = ctx->total[0] & 0x3F;
fill = 64 - left;
ctx->total[0] += (uint32_t) ilen;
ctx->total[0] &= 0xFFFFFFFF;
if( ctx->total[0] < (uint32_t) ilen )
ctx->total[1]++;
if( left && ilen >= fill )
{
memcpy( (void *) (ctx->buffer + left), input, fill );
mbedtls_sha1_process( ctx, ctx->buffer );
input += fill;
ilen -= fill;
left = 0;
}
while( ilen >= 64 )
{
mbedtls_sha1_process( ctx, input );
input += 64;
ilen -= 64;
}
if( ilen > 0 )
memcpy( (void *) (ctx->buffer + left), input, ilen );
}
static const unsigned char sha1_padding[SHA1_BLOCK_LENGTH] =
{
0x80, 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, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/*
* SHA-1 final digest
*/
void mbedtls_sha1_finish( mbedtls_sha1_context *ctx, unsigned char output[SHA1_DIGEST_LENGTH] )
{
uint32_t last, padn;
uint32_t high, low;
unsigned char msglen[8];
high = ( ctx->total[0] >> 29 )
| ( ctx->total[1] << 3 );
low = ( ctx->total[0] << 3 );
PUT_UINT32_BE( high, msglen, 0 );
PUT_UINT32_BE( low, msglen, 4 );
last = ctx->total[0] & 0x3F;
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
mbedtls_sha1_update( ctx, sha1_padding, padn );
mbedtls_sha1_update( ctx, msglen, 8 );
PUT_UINT32_BE( ctx->state[0], output, 0 );
PUT_UINT32_BE( ctx->state[1], output, 4 );
PUT_UINT32_BE( ctx->state[2], output, 8 );
PUT_UINT32_BE( ctx->state[3], output, 12 );
PUT_UINT32_BE( ctx->state[4], output, 16 );
}
/*
* output = SHA-1( input buffer )
*/
void mbedtls_sha1( const unsigned char *input, size_t ilen, unsigned char output[SHA1_DIGEST_LENGTH] )
{
mbedtls_sha1_context ctx;
mbedtls_sha1_init( &ctx );
mbedtls_sha1_starts( &ctx );
mbedtls_sha1_update( &ctx, input, ilen );
mbedtls_sha1_finish( &ctx, output );
mbedtls_sha1_free( &ctx );
}
/*
* Compute HMAC_SHA1 using key, key length, text to hash, size of the text, and output buffer
*/
void HMAC_SHA1(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t out[SHA1_DIGEST_LENGTH]){
uint8_t i;
uint8_t k_ipad[SHA1_BLOCK_LENGTH]; /* inner padding - key XORd with ipad */
uint8_t k_opad[SHA1_BLOCK_LENGTH]; /* outer padding - key XORd with opad */
uint8_t buffer[SHA1_BLOCK_LENGTH + SHA1_DIGEST_LENGTH];
/* start out by storing key in pads */
memset(k_ipad, 0, sizeof(k_ipad));
memset(k_opad, 0, sizeof(k_opad));
if (key_length <= SHA1_BLOCK_LENGTH) {
memcpy(k_ipad, key, key_length);
memcpy(k_opad, key, key_length);
}
else {
mbedtls_sha1(key, key_length, k_ipad);
memcpy(k_opad, k_ipad, SHA1_BLOCK_LENGTH);
}
/* XOR key with ipad and opad values */
for (i = 0; i < SHA1_BLOCK_LENGTH; i++) {
k_ipad[i] ^= HMAC_IPAD;
k_opad[i] ^= HMAC_OPAD;
}
// perform inner SHA1
memcpy(buffer, k_ipad, SHA1_BLOCK_LENGTH);
memcpy(buffer + SHA1_BLOCK_LENGTH, in, n);
mbedtls_sha1(buffer, SHA1_BLOCK_LENGTH + n, out);
memset(buffer, 0, SHA1_BLOCK_LENGTH + n);
// perform outer SHA1
memcpy(buffer, k_opad, SHA1_BLOCK_LENGTH);
memcpy(buffer + SHA1_BLOCK_LENGTH, out, SHA1_DIGEST_LENGTH);
mbedtls_sha1(buffer, SHA1_BLOCK_LENGTH + SHA1_DIGEST_LENGTH, out);
}
/*
* Compute TOTP_HMAC_SHA1 using key, key length, text to hash, size of the text
*/
uint32_t TOTP_HMAC_SHA1(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n){
// STEP 1, get the HMAC-SHA1 hash from counter and key
uint8_t hash[SHA1_DIGEST_LENGTH];
HMAC_SHA1(key, key_length, in, n, hash);
// STEP 2, apply dynamic truncation to obtain a 4-bytes string
uint32_t truncated_hash = 0;
uint8_t _offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF;
uint8_t j;
for (j = 0; j < 4; ++j) {
truncated_hash <<= 8;
truncated_hash |= hash[_offset + j];
}
// STEP 3, compute the OTP value
truncated_hash &= 0x7FFFFFFF; //Disabled
truncated_hash %= 1000000;
return truncated_hash;
}

98
legacy/lib/TOTP/sha1.h Normal file
View File

@@ -0,0 +1,98 @@
/**
* \file sha1.h
*
* \brief SHA-1 cryptographic hash function
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#ifndef MBEDTLS_SHA1_H
#define MBEDTLS_SHA1_H
#define SHA1_DIGEST_LENGTH 20
#define SHA1_BLOCK_LENGTH 64
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
#include <stddef.h>
#include <stdint.h>
/**
* \brief SHA-1 context structure
*/
typedef struct
{
uint32_t total[2]; /*!< number of bytes processed */
uint32_t state[5]; /*!< intermediate digest state */
unsigned char buffer[SHA1_BLOCK_LENGTH]; /*!< data block being processed */
}
mbedtls_sha1_context;
/**
* \brief Initialize SHA-1 context
*
* \param ctx SHA-1 context to be initialized
*/
void mbedtls_sha1_init( mbedtls_sha1_context *ctx );
/**
* \brief Clear SHA-1 context
*
* \param ctx SHA-1 context to be cleared
*/
void mbedtls_sha1_free( mbedtls_sha1_context *ctx );
/**
* \brief SHA-1 context setup
*
* \param ctx context to be initialized
*/
void mbedtls_sha1_starts( mbedtls_sha1_context *ctx );
/**
* \brief SHA-1 process buffer
*
* \param ctx SHA-1 context
* \param input buffer holding the data
* \param ilen length of the input data
*/
void mbedtls_sha1_update( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen );
/**
* \brief SHA-1 final digest
*
* \param ctx SHA-1 context
* \param output SHA-1 checksum result
*/
void mbedtls_sha1_finish( mbedtls_sha1_context *ctx, unsigned char output[SHA1_DIGEST_LENGTH] );
/* Internal use */
void mbedtls_sha1_process( mbedtls_sha1_context *ctx, const unsigned char data[SHA1_BLOCK_LENGTH] );
/**
* \brief Output = SHA-1( input buffer )
*
* \param input buffer holding the data
* \param ilen length of the input data
* \param output SHA-1 checksum result
*/
void mbedtls_sha1( const unsigned char *input, size_t ilen, unsigned char output[SHA1_DIGEST_LENGTH] );
void HMAC_SHA1(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t out[SHA1_DIGEST_LENGTH]);
uint32_t TOTP_HMAC_SHA1(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n);
#endif /* mbedtls_sha1.h */

372
legacy/lib/TOTP/sha256.c Normal file
View File

@@ -0,0 +1,372 @@
/*
* FIPS-180-2 compliant SHA-256 implementation
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
/*
* The SHA-256 Secure Hash Standard was published by NIST in 2002.
*
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
*/
#include "sha256.h"
#include <string.h>
#include <stdio.h>
/* Implementation that should never be optimized out by the compiler */
static void mbedtls_zeroize( void *v, size_t n ) {
volatile unsigned char *p = v; while( n-- ) *p++ = 0;
}
/*
* 32-bit integer manipulation macros (big endian)
*/
#ifndef GET_UINT32_BE
#define GET_UINT32_BE(n,b,i) \
do { \
(n) = ( (uint32_t) (b)[(i) ] << 24 ) \
| ( (uint32_t) (b)[(i) + 1] << 16 ) \
| ( (uint32_t) (b)[(i) + 2] << 8 ) \
| ( (uint32_t) (b)[(i) + 3] ); \
} while( 0 )
#endif
#ifndef PUT_UINT32_BE
#define PUT_UINT32_BE(n,b,i) \
do { \
(b)[(i) ] = (unsigned char) ( (n) >> 24 ); \
(b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \
(b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \
(b)[(i) + 3] = (unsigned char) ( (n) ); \
} while( 0 )
#endif
void mbedtls_sha256_init( mbedtls_sha256_context *ctx )
{
memset( ctx, 0, sizeof( mbedtls_sha256_context ) );
}
void mbedtls_sha256_free( mbedtls_sha256_context *ctx )
{
if( ctx == NULL )
return;
mbedtls_zeroize( ctx, sizeof( mbedtls_sha256_context ) );
}
void mbedtls_sha256_clone( mbedtls_sha256_context *dst,
const mbedtls_sha256_context *src )
{
*dst = *src;
}
/*
* SHA-256 context setup
*/
void mbedtls_sha256_starts( mbedtls_sha256_context *ctx, int is224 )
{
ctx->total[0] = 0;
ctx->total[1] = 0;
if( is224 == 0 )
{
/* SHA-256 */
ctx->state[0] = 0x6A09E667;
ctx->state[1] = 0xBB67AE85;
ctx->state[2] = 0x3C6EF372;
ctx->state[3] = 0xA54FF53A;
ctx->state[4] = 0x510E527F;
ctx->state[5] = 0x9B05688C;
ctx->state[6] = 0x1F83D9AB;
ctx->state[7] = 0x5BE0CD19;
}
else
{
/* SHA-224 */
ctx->state[0] = 0xC1059ED8;
ctx->state[1] = 0x367CD507;
ctx->state[2] = 0x3070DD17;
ctx->state[3] = 0xF70E5939;
ctx->state[4] = 0xFFC00B31;
ctx->state[5] = 0x68581511;
ctx->state[6] = 0x64F98FA7;
ctx->state[7] = 0xBEFA4FA4;
}
ctx->is224 = is224;
}
static const uint32_t K[] =
{
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
};
#define SHR(x,n) ((x & 0xFFFFFFFF) >> n)
#define ROTR(x,n) (SHR(x,n) | (x << (32 - n)))
#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3))
#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10))
#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22))
#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25))
#define F0(x,y,z) ((x & y) | (z & (x | y)))
#define F1(x,y,z) (z ^ (x & (y ^ z)))
#define R(t) \
( \
W[t] = S1(W[t - 2]) + W[t - 7] + \
S0(W[t - 15]) + W[t - 16] \
)
#define P(a,b,c,d,e,f,g,h,x,K) \
{ \
temp1 = h + S3(e) + F1(e,f,g) + K + x; \
temp2 = S2(a) + F0(a,b,c); \
d += temp1; h = temp1 + temp2; \
}
void mbedtls_sha256_process( mbedtls_sha256_context *ctx, const unsigned char data[SHA256_BLOCK_LENGTH] )
{
uint32_t temp1, temp2, W[64];
uint32_t A[8];
unsigned int i;
for( i = 0; i < 8; i++ )
A[i] = ctx->state[i];
for( i = 0; i < 16; i++ )
GET_UINT32_BE( W[i], data, 4 * i );
for( i = 0; i < 16; i += 8 )
{
P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i+0], K[i+0] );
P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[i+1], K[i+1] );
P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[i+2], K[i+2] );
P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], W[i+3], K[i+3] );
P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], W[i+4], K[i+4] );
P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], W[i+5], K[i+5] );
P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], W[i+6], K[i+6] );
P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], W[i+7], K[i+7] );
}
for( i = 16; i < 64; i += 8 )
{
P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], R(i+0), K[i+0] );
P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], R(i+1), K[i+1] );
P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], R(i+2), K[i+2] );
P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], R(i+3), K[i+3] );
P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], R(i+4), K[i+4] );
P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], R(i+5), K[i+5] );
P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], R(i+6), K[i+6] );
P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], R(i+7), K[i+7] );
}
for( i = 0; i < 8; i++ )
ctx->state[i] += A[i];
}
/*
* SHA-256 process buffer
*/
void mbedtls_sha256_update( mbedtls_sha256_context *ctx, const unsigned char *input,
size_t ilen )
{
size_t fill;
uint32_t left;
if( ilen == 0 )
return;
left = ctx->total[0] & 0x3F;
fill = 64 - left;
ctx->total[0] += (uint32_t) ilen;
ctx->total[0] &= 0xFFFFFFFF;
if( ctx->total[0] < (uint32_t) ilen )
ctx->total[1]++;
if( left && ilen >= fill )
{
memcpy( (void *) (ctx->buffer + left), input, fill );
mbedtls_sha256_process( ctx, ctx->buffer );
input += fill;
ilen -= fill;
left = 0;
}
while( ilen >= 64 )
{
mbedtls_sha256_process( ctx, input );
input += 64;
ilen -= 64;
}
if( ilen > 0 )
memcpy( (void *) (ctx->buffer + left), input, ilen );
}
static const unsigned char sha256_padding[SHA256_BLOCK_LENGTH] =
{
0x80, 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, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/*
* SHA-256 final digest
*/
void mbedtls_sha256_finish( mbedtls_sha256_context *ctx, unsigned char* output )
{
uint32_t last, padn;
uint32_t high, low;
unsigned char msglen[8];
high = ( ctx->total[0] >> 29 )
| ( ctx->total[1] << 3 );
low = ( ctx->total[0] << 3 );
PUT_UINT32_BE( high, msglen, 0 );
PUT_UINT32_BE( low, msglen, 4 );
last = ctx->total[0] & 0x3F;
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
mbedtls_sha256_update( ctx, sha256_padding, padn );
mbedtls_sha256_update( ctx, msglen, 8 );
PUT_UINT32_BE( ctx->state[0], output, 0 );
PUT_UINT32_BE( ctx->state[1], output, 4 );
PUT_UINT32_BE( ctx->state[2], output, 8 );
PUT_UINT32_BE( ctx->state[3], output, 12 );
PUT_UINT32_BE( ctx->state[4], output, 16 );
PUT_UINT32_BE( ctx->state[5], output, 20 );
PUT_UINT32_BE( ctx->state[6], output, 24 );
if( ctx->is224 == 0 )
PUT_UINT32_BE( ctx->state[7], output, 28 );
}
/*
* output = SHA-256( input buffer )
*/
void mbedtls_sha256( const unsigned char *input, size_t ilen,
unsigned char* output, int is224 )
{
mbedtls_sha256_context ctx;
mbedtls_sha256_init( &ctx );
mbedtls_sha256_starts( &ctx, is224 );
mbedtls_sha256_update( &ctx, input, ilen );
mbedtls_sha256_finish( &ctx, output );
mbedtls_sha256_free( &ctx );
}
/*
* Compute HMAC_SHA224/256 using key, key length, text to hash, size of the text, output buffer and a switch for SHA224
*/
void HMAC_SHA256(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t* out, int is224){
int digest_length = SHA256_DIGEST_LENGTH;
if (is224 == 1) {
digest_length = SHA224_DIGEST_LENGTH;
}
uint8_t i;
uint8_t k_ipad[SHA256_BLOCK_LENGTH]; /* inner padding - key XORd with ipad */
uint8_t k_opad[SHA256_BLOCK_LENGTH]; /* outer padding - key XORd with opad */
uint8_t buffer[SHA256_BLOCK_LENGTH + digest_length];
/* start out by storing key in pads */
memset(k_ipad, 0, sizeof(k_ipad));
memset(k_opad, 0, sizeof(k_opad));
if (key_length <= SHA256_BLOCK_LENGTH) {
memcpy(k_ipad, key, key_length);
memcpy(k_opad, key, key_length);
}
else {
mbedtls_sha256(key, key_length, k_ipad, is224);
memcpy(k_opad, k_ipad, SHA256_BLOCK_LENGTH);
}
/* XOR key with ipad and opad values */
for (i = 0; i < SHA256_BLOCK_LENGTH; i++) {
k_ipad[i] ^= HMAC_IPAD;
k_opad[i] ^= HMAC_OPAD;
}
// perform inner SHA256
memcpy(buffer, k_ipad, SHA256_BLOCK_LENGTH);
memcpy(buffer + SHA256_BLOCK_LENGTH, in, n);
mbedtls_sha256(buffer, SHA256_BLOCK_LENGTH + n, out, is224);
memset(buffer, 0, SHA256_BLOCK_LENGTH + n);
// perform outer SHA256
memcpy(buffer, k_opad, SHA256_BLOCK_LENGTH);
memcpy(buffer + SHA256_BLOCK_LENGTH, out, digest_length);
mbedtls_sha256(buffer, SHA256_BLOCK_LENGTH + digest_length, out, is224);
}
/*
* Compute TOTP_HMAC_SHA224/256 using key, key length, text to hash, size of the text and a switch for SHA224
*/
uint32_t TOTP_HMAC_SHA256(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, int is224){
int digest_length = SHA256_DIGEST_LENGTH;
if (is224 == 1) {
digest_length = SHA224_DIGEST_LENGTH;
}
// STEP 1, get the HMAC-SHA256 hash from counter and key
uint8_t hash[digest_length];
HMAC_SHA256(key, key_length, in, n, hash, is224);
// STEP 2, apply dynamic truncation to obtain a 4-bytes string
uint32_t truncated_hash = 0;
uint8_t _offset = hash[digest_length - 1] & 0xF;
uint8_t j;
for (j = 0; j < 4; ++j) {
truncated_hash <<= 8;
truncated_hash |= hash[_offset + j];
}
// STEP 3, compute the OTP value
truncated_hash &= 0x7FFFFFFF; //Disabled
truncated_hash %= 1000000;
return truncated_hash;
}

112
legacy/lib/TOTP/sha256.h Normal file
View File

@@ -0,0 +1,112 @@
/**
* \file sha256.h
*
* \brief SHA-224 and SHA-256 cryptographic hash function
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#ifndef MBEDTLS_SHA256_H
#define MBEDTLS_SHA256_H
#define SHA224_DIGEST_LENGTH 28
#define SHA256_DIGEST_LENGTH 32
#define SHA256_BLOCK_LENGTH 64
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
#include <stddef.h>
#include <stdint.h>
/**
* \brief SHA-256 context structure
*/
typedef struct
{
uint32_t total[2]; /*!< number of bytes processed */
uint32_t state[8]; /*!< intermediate digest state */
unsigned char buffer[SHA256_BLOCK_LENGTH]; /*!< data block being processed */
int is224; /*!< 0 => SHA-256, else SHA-224 */
}
mbedtls_sha256_context;
/**
* \brief Initialize SHA-256 context
*
* \param ctx SHA-256 context to be initialized
*/
void mbedtls_sha256_init( mbedtls_sha256_context *ctx );
/**
* \brief Clear SHA-256 context
*
* \param ctx SHA-256 context to be cleared
*/
void mbedtls_sha256_free( mbedtls_sha256_context *ctx );
/**
* \brief Clone (the state of) a SHA-256 context
*
* \param dst The destination context
* \param src The context to be cloned
*/
void mbedtls_sha256_clone( mbedtls_sha256_context *dst,
const mbedtls_sha256_context *src );
/**
* \brief SHA-256 context setup
*
* \param ctx context to be initialized
* \param is224 0 = use SHA256, 1 = use SHA224
*/
void mbedtls_sha256_starts( mbedtls_sha256_context *ctx, int is224 );
/**
* \brief SHA-256 process buffer
*
* \param ctx SHA-256 context
* \param input buffer holding the data
* \param ilen length of the input data
*/
void mbedtls_sha256_update( mbedtls_sha256_context *ctx, const unsigned char *input,
size_t ilen );
/**
* \brief SHA-256 final digest
*
* \param ctx SHA-256 context
* \param output SHA-224/256 checksum result
*/
void mbedtls_sha256_finish( mbedtls_sha256_context *ctx, unsigned char* output );
/* Internal use */
void mbedtls_sha256_process( mbedtls_sha256_context *ctx, const unsigned char data[SHA256_BLOCK_LENGTH] );
/**
* \brief Output = SHA-256( input buffer )
*
* \param input buffer holding the data
* \param ilen length of the input data
* \param output SHA-224/256 checksum result
* \param is224 0 = use SHA256, 1 = use SHA224
*/
void mbedtls_sha256( const unsigned char *input, size_t ilen,
unsigned char* output, int is224 );
void HMAC_SHA256(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t* out, int is224);
uint32_t TOTP_HMAC_SHA256(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, int is224);
#endif /* mbedtls_sha256.h */

422
legacy/lib/TOTP/sha512.c Normal file
View File

@@ -0,0 +1,422 @@
/*
* FIPS-180-2 compliant SHA-384/512 implementation
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
/*
* The SHA-512 Secure Hash Standard was published by NIST in 2002.
*
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
*/
#include "sha512.h"
#include <string.h>
#include <stdio.h>
#if defined(_MSC_VER) || defined(__WATCOMC__)
#define UL64(x) x##ui64
#else
#define UL64(x) x##ULL
#endif
/* Implementation that should never be optimized out by the compiler */
static void mbedtls_zeroize( void *v, size_t n ) {
volatile unsigned char *p = v; while( n-- ) *p++ = 0;
}
/*
* 64-bit integer manipulation macros (big endian)
*/
#ifndef GET_UINT64_BE
#define GET_UINT64_BE(n,b,i) \
{ \
(n) = ( (uint64_t) (b)[(i) ] << 56 ) \
| ( (uint64_t) (b)[(i) + 1] << 48 ) \
| ( (uint64_t) (b)[(i) + 2] << 40 ) \
| ( (uint64_t) (b)[(i) + 3] << 32 ) \
| ( (uint64_t) (b)[(i) + 4] << 24 ) \
| ( (uint64_t) (b)[(i) + 5] << 16 ) \
| ( (uint64_t) (b)[(i) + 6] << 8 ) \
| ( (uint64_t) (b)[(i) + 7] ); \
}
#endif /* GET_UINT64_BE */
#ifndef PUT_UINT64_BE
#define PUT_UINT64_BE(n,b,i) \
{ \
(b)[(i) ] = (unsigned char) ( (n) >> 56 ); \
(b)[(i) + 1] = (unsigned char) ( (n) >> 48 ); \
(b)[(i) + 2] = (unsigned char) ( (n) >> 40 ); \
(b)[(i) + 3] = (unsigned char) ( (n) >> 32 ); \
(b)[(i) + 4] = (unsigned char) ( (n) >> 24 ); \
(b)[(i) + 5] = (unsigned char) ( (n) >> 16 ); \
(b)[(i) + 6] = (unsigned char) ( (n) >> 8 ); \
(b)[(i) + 7] = (unsigned char) ( (n) ); \
}
#endif /* PUT_UINT64_BE */
/*
* Round constants
*/
static const uint64_t K[80] =
{
UL64(0x428A2F98D728AE22), UL64(0x7137449123EF65CD),
UL64(0xB5C0FBCFEC4D3B2F), UL64(0xE9B5DBA58189DBBC),
UL64(0x3956C25BF348B538), UL64(0x59F111F1B605D019),
UL64(0x923F82A4AF194F9B), UL64(0xAB1C5ED5DA6D8118),
UL64(0xD807AA98A3030242), UL64(0x12835B0145706FBE),
UL64(0x243185BE4EE4B28C), UL64(0x550C7DC3D5FFB4E2),
UL64(0x72BE5D74F27B896F), UL64(0x80DEB1FE3B1696B1),
UL64(0x9BDC06A725C71235), UL64(0xC19BF174CF692694),
UL64(0xE49B69C19EF14AD2), UL64(0xEFBE4786384F25E3),
UL64(0x0FC19DC68B8CD5B5), UL64(0x240CA1CC77AC9C65),
UL64(0x2DE92C6F592B0275), UL64(0x4A7484AA6EA6E483),
UL64(0x5CB0A9DCBD41FBD4), UL64(0x76F988DA831153B5),
UL64(0x983E5152EE66DFAB), UL64(0xA831C66D2DB43210),
UL64(0xB00327C898FB213F), UL64(0xBF597FC7BEEF0EE4),
UL64(0xC6E00BF33DA88FC2), UL64(0xD5A79147930AA725),
UL64(0x06CA6351E003826F), UL64(0x142929670A0E6E70),
UL64(0x27B70A8546D22FFC), UL64(0x2E1B21385C26C926),
UL64(0x4D2C6DFC5AC42AED), UL64(0x53380D139D95B3DF),
UL64(0x650A73548BAF63DE), UL64(0x766A0ABB3C77B2A8),
UL64(0x81C2C92E47EDAEE6), UL64(0x92722C851482353B),
UL64(0xA2BFE8A14CF10364), UL64(0xA81A664BBC423001),
UL64(0xC24B8B70D0F89791), UL64(0xC76C51A30654BE30),
UL64(0xD192E819D6EF5218), UL64(0xD69906245565A910),
UL64(0xF40E35855771202A), UL64(0x106AA07032BBD1B8),
UL64(0x19A4C116B8D2D0C8), UL64(0x1E376C085141AB53),
UL64(0x2748774CDF8EEB99), UL64(0x34B0BCB5E19B48A8),
UL64(0x391C0CB3C5C95A63), UL64(0x4ED8AA4AE3418ACB),
UL64(0x5B9CCA4F7763E373), UL64(0x682E6FF3D6B2B8A3),
UL64(0x748F82EE5DEFB2FC), UL64(0x78A5636F43172F60),
UL64(0x84C87814A1F0AB72), UL64(0x8CC702081A6439EC),
UL64(0x90BEFFFA23631E28), UL64(0xA4506CEBDE82BDE9),
UL64(0xBEF9A3F7B2C67915), UL64(0xC67178F2E372532B),
UL64(0xCA273ECEEA26619C), UL64(0xD186B8C721C0C207),
UL64(0xEADA7DD6CDE0EB1E), UL64(0xF57D4F7FEE6ED178),
UL64(0x06F067AA72176FBA), UL64(0x0A637DC5A2C898A6),
UL64(0x113F9804BEF90DAE), UL64(0x1B710B35131C471B),
UL64(0x28DB77F523047D84), UL64(0x32CAAB7B40C72493),
UL64(0x3C9EBE0A15C9BEBC), UL64(0x431D67C49C100D4C),
UL64(0x4CC5D4BECB3E42B6), UL64(0x597F299CFC657E2A),
UL64(0x5FCB6FAB3AD6FAEC), UL64(0x6C44198C4A475817)
};
void mbedtls_sha512_init( mbedtls_sha512_context *ctx )
{
memset( ctx, 0, sizeof( mbedtls_sha512_context ) );
}
void mbedtls_sha512_free( mbedtls_sha512_context *ctx )
{
if( ctx == NULL )
return;
mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) );
}
void mbedtls_sha512_clone( mbedtls_sha512_context *dst,
const mbedtls_sha512_context *src )
{
*dst = *src;
}
/*
* SHA-512 context setup
*/
void mbedtls_sha512_starts( mbedtls_sha512_context *ctx, int is384 )
{
ctx->total[0] = 0;
ctx->total[1] = 0;
if( is384 == 0 )
{
/* SHA-512 */
ctx->state[0] = UL64(0x6A09E667F3BCC908);
ctx->state[1] = UL64(0xBB67AE8584CAA73B);
ctx->state[2] = UL64(0x3C6EF372FE94F82B);
ctx->state[3] = UL64(0xA54FF53A5F1D36F1);
ctx->state[4] = UL64(0x510E527FADE682D1);
ctx->state[5] = UL64(0x9B05688C2B3E6C1F);
ctx->state[6] = UL64(0x1F83D9ABFB41BD6B);
ctx->state[7] = UL64(0x5BE0CD19137E2179);
}
else
{
/* SHA-384 */
ctx->state[0] = UL64(0xCBBB9D5DC1059ED8);
ctx->state[1] = UL64(0x629A292A367CD507);
ctx->state[2] = UL64(0x9159015A3070DD17);
ctx->state[3] = UL64(0x152FECD8F70E5939);
ctx->state[4] = UL64(0x67332667FFC00B31);
ctx->state[5] = UL64(0x8EB44A8768581511);
ctx->state[6] = UL64(0xDB0C2E0D64F98FA7);
ctx->state[7] = UL64(0x47B5481DBEFA4FA4);
}
ctx->is384 = is384;
}
void mbedtls_sha512_process( mbedtls_sha512_context *ctx, const unsigned char data[SHA512_BLOCK_LENGTH] )
{
int i;
uint64_t temp1, temp2, W[80];
uint64_t A, B, C, D, E, F, G, H;
#define SHR(x,n) (x >> n)
#define ROTR(x,n) (SHR(x,n) | (x << (64 - n)))
#define S0(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7))
#define S1(x) (ROTR(x,19) ^ ROTR(x,61) ^ SHR(x, 6))
#define S2(x) (ROTR(x,28) ^ ROTR(x,34) ^ ROTR(x,39))
#define S3(x) (ROTR(x,14) ^ ROTR(x,18) ^ ROTR(x,41))
#define F0(x,y,z) ((x & y) | (z & (x | y)))
#define F1(x,y,z) (z ^ (x & (y ^ z)))
#define P(a,b,c,d,e,f,g,h,x,K) \
{ \
temp1 = h + S3(e) + F1(e,f,g) + K + x; \
temp2 = S2(a) + F0(a,b,c); \
d += temp1; h = temp1 + temp2; \
}
for( i = 0; i < 16; i++ )
{
GET_UINT64_BE( W[i], data, i << 3 );
}
for( ; i < 80; i++ )
{
W[i] = S1(W[i - 2]) + W[i - 7] +
S0(W[i - 15]) + W[i - 16];
}
A = ctx->state[0];
B = ctx->state[1];
C = ctx->state[2];
D = ctx->state[3];
E = ctx->state[4];
F = ctx->state[5];
G = ctx->state[6];
H = ctx->state[7];
i = 0;
do
{
P( A, B, C, D, E, F, G, H, W[i], K[i] ); i++;
P( H, A, B, C, D, E, F, G, W[i], K[i] ); i++;
P( G, H, A, B, C, D, E, F, W[i], K[i] ); i++;
P( F, G, H, A, B, C, D, E, W[i], K[i] ); i++;
P( E, F, G, H, A, B, C, D, W[i], K[i] ); i++;
P( D, E, F, G, H, A, B, C, W[i], K[i] ); i++;
P( C, D, E, F, G, H, A, B, W[i], K[i] ); i++;
P( B, C, D, E, F, G, H, A, W[i], K[i] ); i++;
}
while( i < 80 );
ctx->state[0] += A;
ctx->state[1] += B;
ctx->state[2] += C;
ctx->state[3] += D;
ctx->state[4] += E;
ctx->state[5] += F;
ctx->state[6] += G;
ctx->state[7] += H;
}
/*
* SHA-512 process buffer
*/
void mbedtls_sha512_update( mbedtls_sha512_context *ctx, const unsigned char *input,
size_t ilen )
{
size_t fill;
unsigned int left;
if( ilen == 0 )
return;
left = (unsigned int) (ctx->total[0] & 0x7F);
fill = 128 - left;
ctx->total[0] += (uint64_t) ilen;
if( ctx->total[0] < (uint64_t) ilen )
ctx->total[1]++;
if( left && ilen >= fill )
{
memcpy( (void *) (ctx->buffer + left), input, fill );
mbedtls_sha512_process( ctx, ctx->buffer );
input += fill;
ilen -= fill;
left = 0;
}
while( ilen >= 128 )
{
mbedtls_sha512_process( ctx, input );
input += 128;
ilen -= 128;
}
if( ilen > 0 )
memcpy( (void *) (ctx->buffer + left), input, ilen );
}
static const unsigned char sha512_padding[SHA512_BLOCK_LENGTH] =
{
0x80, 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, 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, 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
};
/*
* SHA-512 final digest
*/
void mbedtls_sha512_finish( mbedtls_sha512_context *ctx, unsigned char* output )
{
size_t last, padn;
uint64_t high, low;
unsigned char msglen[16];
high = ( ctx->total[0] >> 61 )
| ( ctx->total[1] << 3 );
low = ( ctx->total[0] << 3 );
PUT_UINT64_BE( high, msglen, 0 );
PUT_UINT64_BE( low, msglen, 8 );
last = (size_t)( ctx->total[0] & 0x7F );
padn = ( last < 112 ) ? ( 112 - last ) : ( 240 - last );
mbedtls_sha512_update( ctx, sha512_padding, padn );
mbedtls_sha512_update( ctx, msglen, 16 );
PUT_UINT64_BE( ctx->state[0], output, 0 );
PUT_UINT64_BE( ctx->state[1], output, 8 );
PUT_UINT64_BE( ctx->state[2], output, 16 );
PUT_UINT64_BE( ctx->state[3], output, 24 );
PUT_UINT64_BE( ctx->state[4], output, 32 );
PUT_UINT64_BE( ctx->state[5], output, 40 );
if( ctx->is384 == 0 )
{
PUT_UINT64_BE( ctx->state[6], output, 48 );
PUT_UINT64_BE( ctx->state[7], output, 56 );
}
}
/*
* output = SHA-512( input buffer )
*/
void mbedtls_sha512( const unsigned char *input, size_t ilen,
unsigned char* output, int is384 )
{
mbedtls_sha512_context ctx;
mbedtls_sha512_init( &ctx );
mbedtls_sha512_starts( &ctx, is384 );
mbedtls_sha512_update( &ctx, input, ilen );
mbedtls_sha512_finish( &ctx, output );
mbedtls_sha512_free( &ctx );
}
/*
* Compute HMAC_SHA384/512 using key, key length, text to hash, size of the text, output buffer and a switch for SHA384
*/
void HMAC_SHA512(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t* out, int is384){
int digest_length = SHA512_DIGEST_LENGTH;
if (is384 == 1) {
digest_length = SHA384_DIGEST_LENGTH;
}
uint8_t i;
uint8_t k_ipad[SHA512_BLOCK_LENGTH]; /* inner padding - key XORd with ipad */
uint8_t k_opad[SHA512_BLOCK_LENGTH]; /* outer padding - key XORd with opad */
uint8_t buffer[SHA512_BLOCK_LENGTH + digest_length];
/* start out by storing key in pads */
memset(k_ipad, 0, sizeof(k_ipad));
memset(k_opad, 0, sizeof(k_opad));
if (key_length <= SHA512_BLOCK_LENGTH) {
memcpy(k_ipad, key, key_length);
memcpy(k_opad, key, key_length);
}
else {
mbedtls_sha512(key, key_length, k_ipad, is384);
memcpy(k_opad, k_ipad, SHA512_BLOCK_LENGTH);
}
/* XOR key with ipad and opad values */
for (i = 0; i < SHA512_BLOCK_LENGTH; i++) {
k_ipad[i] ^= HMAC_IPAD;
k_opad[i] ^= HMAC_OPAD;
}
// perform inner SHA512
memcpy(buffer, k_ipad, SHA512_BLOCK_LENGTH);
memcpy(buffer + SHA512_BLOCK_LENGTH, in, n);
mbedtls_sha512(buffer, SHA512_BLOCK_LENGTH + n, out, is384);
memset(buffer, 0, SHA512_BLOCK_LENGTH + n);
// perform outer SHA512
memcpy(buffer, k_opad, SHA512_BLOCK_LENGTH);
memcpy(buffer + SHA512_BLOCK_LENGTH, out, digest_length);
mbedtls_sha512(buffer, SHA512_BLOCK_LENGTH + digest_length, out, is384);
}
/*
* Compute TOTP_HMAC_SHA384/512 using key, key length, text to hash, size of the text and a switch for SHA384
*/
uint32_t TOTP_HMAC_SHA512(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, int is384){
int digest_length = SHA512_DIGEST_LENGTH;
if (is384 == 1) {
digest_length = SHA384_DIGEST_LENGTH;
}
// STEP 1, get the HMAC-SHA512 hash from counter and key
uint8_t hash[digest_length];
HMAC_SHA512(key, key_length, in, n, hash, is384);
// STEP 2, apply dynamic truncation to obtain a 4-bytes string
uint32_t truncated_hash = 0;
uint8_t _offset = hash[digest_length - 1] & 0xF;
uint8_t j;
for (j = 0; j < 4; ++j) {
truncated_hash <<= 8;
truncated_hash |= hash[_offset + j];
}
// STEP 3, compute the OTP value
truncated_hash &= 0x7FFFFFFF; //Disabled
truncated_hash %= 1000000;
return truncated_hash;
}

119
legacy/lib/TOTP/sha512.h Normal file
View File

@@ -0,0 +1,119 @@
/**
* \file sha512.h
*
* \brief SHA-384 and SHA-512 cryptographic hash function
*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#ifndef MBEDTLS_SHA512_H
#define MBEDTLS_SHA512_H
#define SHA384_DIGEST_LENGTH 48
#define SHA512_DIGEST_LENGTH 64
#define SHA512_BLOCK_LENGTH 128
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
#include <stddef.h>
#include <stdint.h>
/**
* \brief SHA-512 context structure
*/
typedef struct
{
uint64_t total[2]; /*!< number of bytes processed */
uint64_t state[8]; /*!< intermediate digest state */
unsigned char buffer[SHA512_BLOCK_LENGTH]; /*!< data block being processed */
int is384; /*!< 0 => SHA-512, else SHA-384 */
}
mbedtls_sha512_context;
/**
* \brief Initialize SHA-512 context
*
* \param ctx SHA-512 context to be initialized
*/
void mbedtls_sha512_init( mbedtls_sha512_context *ctx );
/**
* \brief Clear SHA-512 context
*
* \param ctx SHA-512 context to be cleared
*/
void mbedtls_sha512_free( mbedtls_sha512_context *ctx );
/**
* \brief Clone (the state of) a SHA-512 context
*
* \param dst The destination context
* \param src The context to be cloned
*/
void mbedtls_sha512_clone( mbedtls_sha512_context *dst,
const mbedtls_sha512_context *src );
/**
* \brief SHA-512 context setup
*
* \param ctx context to be initialized
* \param is384 0 = use SHA512, 1 = use SHA384
*/
void mbedtls_sha512_starts( mbedtls_sha512_context *ctx, int is384 );
/**
* \brief SHA-512 process buffer
*
* \param ctx SHA-512 context
* \param input buffer holding the data
* \param ilen length of the input data
*/
void mbedtls_sha512_update( mbedtls_sha512_context *ctx, const unsigned char *input,
size_t ilen );
/**
* \brief SHA-512 final digest
*
* \param ctx SHA-512 context
* \param output SHA-384/512 checksum result
*/
void mbedtls_sha512_finish( mbedtls_sha512_context *ctx, unsigned char* output );
/**
* \brief Output = SHA-512( input buffer )
*
* \param input buffer holding the data
* \param ilen length of the input data
* \param output SHA-384/512 checksum result
* \param is384 0 = use SHA512, 1 = use SHA384
*/
void mbedtls_sha512( const unsigned char *input, size_t ilen,
unsigned char* output, int is384 );
/**
* \brief Checkup routine
*
* \return 0 if successful, or 1 if the test failed
*/
int mbedtls_sha512_self_test( int verbose );
/* Internal use */
void mbedtls_sha512_process( mbedtls_sha512_context *ctx, const unsigned char data[SHA512_BLOCK_LENGTH] );
void HMAC_SHA512(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, uint8_t* out, int is384);
uint32_t TOTP_HMAC_SHA512(const uint8_t* key, size_t key_length, const uint8_t *in, size_t n, int is384);
#endif /* mbedtls_sha512.h */

View File

@@ -0,0 +1,9 @@
Public domain
THIS 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.

View File

@@ -0,0 +1,9 @@
# Astrolib
This is a relatively straightforward C port of Greg Miller's [JavaScript astro library](https://github.com/gmiller123456/astrogreg), done by Joey Castillo, for the Sensor Watch Astronomy watch face.
He released his work into the public domain and so I release this into the public domain as well.
I have tested the output of this library against [NASA's Horizons system](https://ssd.jpl.nasa.gov/horizons/app.html#/) and found the results to match (within reason, as we're using a truncated version of VSOP87). Moon calculations still seem a bit iffy, but it doesn't surprise me seeing as there are three calculations involved and the error could stack up.
THIS 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.

View File

@@ -0,0 +1,555 @@
/*
* Partial C port of Greg Miller's public domain astro library (gmiller@gregmiller.net) 2019
* https://github.com/gmiller123456/astrogreg
*
* Ported by Joey Castillo for Sensor Watch
* https://github.com/joeycastillo/Sensor-Watch/
*
* Public Domain
*
* THIS 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 <math.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "astrolib.h"
#include "vsop87a_milli.h"
double astro_convert_utc_to_tt(double jd) ;
double astro_get_GMST(double ut1);
astro_cartesian_coordinates_t astro_subtract_cartesian(astro_cartesian_coordinates_t a, astro_cartesian_coordinates_t b);
astro_cartesian_coordinates_t astro_rotate_from_vsop_to_J2000(astro_cartesian_coordinates_t c);
astro_matrix_t astro_get_x_rotation_matrix(double r);
astro_matrix_t astro_get_y_rotation_matrix(double r);
astro_matrix_t astro_get_z_rotation_matrix(double r);
astro_matrix_t astro_transpose_matrix(astro_matrix_t m);
astro_matrix_t astro_dot_product(astro_matrix_t a, astro_matrix_t b);
astro_matrix_t astro_get_precession_matrix(double jd);
astro_cartesian_coordinates_t astro_matrix_multiply(astro_cartesian_coordinates_t v, astro_matrix_t m);
astro_cartesian_coordinates_t astro_convert_geodedic_latlon_to_ITRF_XYZ(double lat, double lon, double height);
astro_cartesian_coordinates_t astro_convert_ITRF_to_GCRS(astro_cartesian_coordinates_t r, double ut1);
astro_cartesian_coordinates_t astro_convert_coordinates_from_meters_to_AU(astro_cartesian_coordinates_t c);
astro_cartesian_coordinates_t astro_get_observer_geocentric_coords(double jd, double lat, double lon);
astro_cartesian_coordinates_t astro_get_body_coordinates(astro_body_t bodyNum, double et);
astro_cartesian_coordinates_t astro_get_body_coordinates_light_time_adjusted(astro_body_t body, astro_cartesian_coordinates_t origin, double t);
astro_equatorial_coordinates_t astro_convert_cartesian_to_polar(astro_cartesian_coordinates_t xyz);
//Special "Math.floor()" function used by convertDateToJulianDate()
static double _astro_special_floor(double d) {
if(d > 0) {
return floor(d);
}
return floor(d) - 1;
}
double astro_convert_date_to_julian_date(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
if (month < 3){
year = year - 1;
month = month + 12;
}
double b = 0;
if (!(year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day < 5))))) {
double a = _astro_special_floor(year / 100.0);
b = 2 - a + _astro_special_floor(a / 4.0);
}
double jd = _astro_special_floor(365.25 * (year + 4716)) + _astro_special_floor(30.6001 * (month + 1)) + day + b - 1524.5;
jd += hour / 24.0;
jd += minute / 24.0 / 60.0;
jd += second / 24.0 / 60.0 / 60.0;
return jd;
}
//Return all values in radians.
//The positions are adjusted for the parallax of the Earth, and the offset of the observer from the Earth's center
//All input and output angles are in radians!
astro_equatorial_coordinates_t astro_get_ra_dec(double jd, astro_body_t body, double lat, double lon, bool calculate_precession) {
double jdTT = astro_convert_utc_to_tt(jd);
double t = astro_convert_jd_to_julian_millenia_since_j2000(jdTT);
// Get current position of Earth and the target body
astro_cartesian_coordinates_t earth_coords = astro_get_body_coordinates(ASTRO_BODY_EARTH, t);
astro_cartesian_coordinates_t body_coords = astro_get_body_coordinates_light_time_adjusted(body, earth_coords, t);
// Convert to Geocentric coordinate
body_coords = astro_subtract_cartesian(body_coords, earth_coords);
//Rotate ecliptic coordinates to J2000 coordinates
body_coords = astro_rotate_from_vsop_to_J2000(body_coords);
astro_matrix_t precession;
// TODO: rotate body for precession, nutation and bias
if(calculate_precession) {
precession = astro_get_precession_matrix(jdTT);
body_coords = astro_matrix_multiply(body_coords, precession);
}
//Convert to topocentric
astro_cartesian_coordinates_t observerXYZ = astro_get_observer_geocentric_coords(jdTT, lat, lon);
if(calculate_precession) {
//TODO: rotate observerXYZ for precession, nutation and bias
astro_matrix_t precessionInv = astro_transpose_matrix(precession);
observerXYZ = astro_matrix_multiply(observerXYZ, precessionInv);
}
body_coords = astro_subtract_cartesian(body_coords, observerXYZ);
//Convert to topocentric RA DEC by converting from cartesian coordinates to polar coordinates
astro_equatorial_coordinates_t retval = astro_convert_cartesian_to_polar(body_coords);
retval.declination = M_PI/2.0 - retval.declination; //Dec. Offset to make 0 the equator, and the poles +/-90 deg
if(retval.right_ascension < 0) retval.right_ascension += 2*M_PI; //Ensure RA is positive
return retval;
}
//Converts a Julian Date in UTC to Terrestrial Time (TT)
double astro_convert_utc_to_tt(double jd) {
//Leap seconds are hard coded, should be updated from the IERS website for other times
//TAI = UTC + leap seconds (e.g. 32)
//TT=TAI + 32.184
//return jd + (32.0 + 32.184) / 24.0 / 60.0 / 60.0;
return jd + (37.0 + 32.184) / 24.0 / 60.0 / 60.0;
/*
https://data.iana.org/time-zones/tzdb-2018a/leap-seconds.list
2272060800 10 # 1 Jan 1972
2287785600 11 # 1 Jul 1972
2303683200 12 # 1 Jan 1973
2335219200 13 # 1 Jan 1974
2366755200 14 # 1 Jan 1975
2398291200 15 # 1 Jan 1976
2429913600 16 # 1 Jan 1977
2461449600 17 # 1 Jan 1978
2492985600 18 # 1 Jan 1979
2524521600 19 # 1 Jan 1980
2571782400 20 # 1 Jul 1981
2603318400 21 # 1 Jul 1982
2634854400 22 # 1 Jul 1983
2698012800 23 # 1 Jul 1985
2776982400 24 # 1 Jan 1988
2840140800 25 # 1 Jan 1990
2871676800 26 # 1 Jan 1991
2918937600 27 # 1 Jul 1992
2950473600 28 # 1 Jul 1993
2982009600 29 # 1 Jul 1994
3029443200 30 # 1 Jan 1996
3076704000 31 # 1 Jul 1997
3124137600 32 # 1 Jan 1999
3345062400 33 # 1 Jan 2006
3439756800 34 # 1 Jan 2009
3550089600 35 # 1 Jul 2012
3644697600 36 # 1 Jul 2015
3692217600 37 # 1 Jan 2017
*/
}
double astro_convert_jd_to_julian_millenia_since_j2000(double jd) {
return (jd - 2451545.0) / 365250.0;
}
astro_cartesian_coordinates_t astro_subtract_cartesian(astro_cartesian_coordinates_t a, astro_cartesian_coordinates_t b) {
astro_cartesian_coordinates_t retval;
retval.x = a.x - b.x;
retval.y = a.y - b.y;
retval.z = a.z - b.z;
return retval;
}
// Performs the rotation from ecliptic coordinates to J2000 coordinates for the given vector x
astro_cartesian_coordinates_t astro_rotate_from_vsop_to_J2000(astro_cartesian_coordinates_t c) {
/* From VSOP87.doc
X +1.000000000000 +0.000000440360 -0.000000190919 X
Y = -0.000000479966 +0.917482137087 -0.397776982902 Y
Z FK5 0.000000000000 +0.397776982902 +0.917482137087 Z VSOP87A
*/
astro_cartesian_coordinates_t t;
t.x = c.x + c.y * 0.000000440360 + c.z * -0.000000190919;
t.y = c.x * -0.000000479966 + c.y * 0.917482137087 + c.z * -0.397776982902;
t.z = c.y * 0.397776982902 + c.z * 0.917482137087;
return t;
}
double astro_get_GMST(double ut1) {
double D = ut1 - 2451545.0;
double T = D/36525.0;
double gmst = fmod((280.46061837 + 360.98564736629*D + 0.000387933*T*T - T*T*T/38710000.0), 360.0);
if(gmst<0) {
gmst+=360;
}
return gmst/15;
}
static astro_matrix_t _astro_get_empty_matrix() {
astro_matrix_t t;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
t.elements[i][j] = 0;
}
}
return t;
}
//Gets a rotation matrix about the x axis. Angle R is in radians
astro_matrix_t astro_get_x_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=1;
t.elements[0][1]=0;
t.elements[0][2]=0;
t.elements[1][0]=0;
t.elements[1][1]=cos(r);
t.elements[1][2]=sin(r);
t.elements[2][0]=0;
t.elements[2][1]=-sin(r);
t.elements[2][2]=cos(r);
return t;
}
//Gets a rotation matrix about the y axis. Angle R is in radians
astro_matrix_t astro_get_y_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=cos(r);
t.elements[0][1]=0;
t.elements[0][2]=-sin(r);
t.elements[1][0]=0;
t.elements[1][1]=1;
t.elements[1][2]=0;
t.elements[2][0]=sin(r);
t.elements[2][1]=0;
t.elements[2][2]=cos(r);
return t;
}
//Gets a rotation matrix about the z axis. Angle R is in radians
astro_matrix_t astro_get_z_rotation_matrix(double r) {
astro_matrix_t t = _astro_get_empty_matrix();
t.elements[0][0]=cos(r);
t.elements[0][1]=sin(r);
t.elements[0][2]=0;
t.elements[1][0]=-sin(r);
t.elements[1][1]=cos(r);
t.elements[1][2]=0;
t.elements[2][0]=0;
t.elements[2][1]=0;
t.elements[2][2]=1;
return t;
}
void astro_print_matrix(char * title, astro_matrix_t matrix);
void astro_print_matrix(char * title, astro_matrix_t matrix) {
printf("%s\n", title);
for(uint8_t i = 0; i < 3 ; i++) {
printf("\t");
for(uint8_t j = 0 ; j < 3 ; j++) {
printf("%12f", matrix.elements[i][j]);
}
printf("\n");
}
printf("\n");
}
astro_matrix_t astro_dot_product(astro_matrix_t a, astro_matrix_t b) {
astro_matrix_t retval;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
double temp = 0;
for(uint8_t k = 0; k < 3 ; k++) {
temp += a.elements[i][k] * b.elements[k][j];
}
retval.elements[i][j]=temp;
}
}
return retval;
}
astro_matrix_t astro_transpose_matrix(astro_matrix_t m) {
astro_matrix_t retval;
for(uint8_t i = 0; i < 3 ; i++) {
for(uint8_t j = 0 ; j < 3 ; j++) {
retval.elements[i][j] = m.elements[j][i];
}
}
return retval;
}
astro_matrix_t astro_get_precession_matrix(double jd) {
//2006 IAU Precession. Implemented from IERS Technical Note No 36 ch5.
//https://www.iers.org/SharedDocs/Publikationen/EN/IERS/Publications/tn/TechnNote36/tn36_043.pdf?__blob=publicationFile&v=1
double t = (jd - 2451545.0) / 36525.0; //5.2
const double Arcsec2Radians = M_PI/180.0/60.0/60.0; //Converts arc seconds used in equations below to radians
double e0 = 84381.406 * Arcsec2Radians; //5.6.4
double omegaA = e0 + ((-0.025754 + (0.0512623 + (-0.00772503 + (-0.000000467 + 0.0000003337*t) * t) * t) * t) * t) * Arcsec2Radians; //5.39
double psiA = ((5038.481507 + (-1.0790069 + (-0.00114045 + (0.000132851 - 0.0000000951*t) * t) * t) * t) * t) * Arcsec2Radians; //5.39
double chiA = ((10.556403 + (-2.3814292 + (-0.00121197 + (0.000170663 - 0.0000000560*t) * t) * t) * t) * t) * Arcsec2Radians; //5.40
//Rotation matrix from 5.4.5
//(R1(e0) · R3(psiA) · R1(omegaA) · R3(chiA))
//Above eq rotates from "of date" to J2000, so we reverse the signs to go from J2000 to "of date"
astro_matrix_t m1 = astro_get_x_rotation_matrix(e0);
astro_matrix_t m2 = astro_get_z_rotation_matrix(-psiA);
astro_matrix_t m3 = astro_get_x_rotation_matrix(-omegaA);
astro_matrix_t m4 = astro_get_z_rotation_matrix(chiA);
astro_matrix_t m5 = astro_dot_product(m4, m3);
astro_matrix_t m6 = astro_dot_product(m5, m2);
astro_matrix_t precessionMatrix = astro_dot_product(m6, m1);
return precessionMatrix;
}
astro_cartesian_coordinates_t astro_matrix_multiply(astro_cartesian_coordinates_t v, astro_matrix_t m) {
astro_cartesian_coordinates_t t;
t.x = v.x*m.elements[0][0] + v.y*m.elements[0][1] + v.z*m.elements[0][2];
t.y = v.x*m.elements[1][0] + v.y*m.elements[1][1] + v.z*m.elements[1][2];
t.z = v.x*m.elements[2][0] + v.y*m.elements[2][1] + v.z*m.elements[2][2];
return t;
}
//Converts cartesian XYZ coordinates to polar (e.g. J2000 xyz to Right Accention and Declication)
astro_equatorial_coordinates_t astro_convert_cartesian_to_polar(astro_cartesian_coordinates_t xyz) {
astro_equatorial_coordinates_t t;
t.distance = sqrt(xyz.x * xyz.x + xyz.y * xyz.y + xyz.z * xyz.z);
t.declination = acos(xyz.z / t.distance);
t.right_ascension = atan2(xyz.y, xyz.x);
if(t.declination < 0) t.declination += 2 * M_PI;
if(t.right_ascension < 0) t.right_ascension += 2 * M_PI;
return t;
}
//Convert Geodedic Lat Lon to geocentric XYZ position vector
//All angles are input as radians
astro_cartesian_coordinates_t astro_convert_geodedic_latlon_to_ITRF_XYZ(double lat, double lon, double height) {
//Algorithm from Explanatory Supplement to the Astronomical Almanac 3rd ed. P294
const double a = 6378136.6;
const double f = 1 / 298.25642;
const double C = sqrt(((cos(lat)*cos(lat)) + (1.0-f)*(1.0-f) * (sin(lat)*sin(lat))));
const double S = (1-f)*(1-f)*C;
double h = height;
astro_cartesian_coordinates_t r;
r.x = (a*C+h) * cos(lat) * cos(lon);
r.y = (a*C+h) * cos(lat) * sin(lon);
r.z = (a*S+h) * sin(lat);
return r;
}
//Convert position vector to celestial "of date" system.
//g(t)=R3(-GAST) r
//(Remember to use UT1 for GAST, not ET)
//All angles are input and output as radians
astro_cartesian_coordinates_t astro_convert_ITRF_to_GCRS(astro_cartesian_coordinates_t r, double ut1) {
//This is a simple rotation matrix implemenation about the Z axis, rotation angle is -GMST
double GMST = astro_get_GMST(ut1);
GMST =- GMST * 15.0 * M_PI / 180.0;
astro_matrix_t m = astro_get_z_rotation_matrix(GMST);
astro_cartesian_coordinates_t t = astro_matrix_multiply(r, m);
return t;
}
astro_cartesian_coordinates_t astro_convert_coordinates_from_meters_to_AU(astro_cartesian_coordinates_t c) {
astro_cartesian_coordinates_t t;
t.x = c.x / 1.49597870691E+11;
t.y = c.y / 1.49597870691E+11;
t.z = c.z / 1.49597870691E+11;
return t;
}
astro_cartesian_coordinates_t astro_get_observer_geocentric_coords(double jd, double lat, double lon) {
astro_cartesian_coordinates_t r = astro_convert_geodedic_latlon_to_ITRF_XYZ(lat, lon,0);
r = astro_convert_ITRF_to_GCRS(r, jd);
r = astro_convert_coordinates_from_meters_to_AU(r);
return r;
}
//Returns a body's cartesian coordinates centered on the Sun.
//Requires vsop87a_milli_js, if you wish to use a different version of VSOP87, replace the class name vsop87a_milli below
astro_cartesian_coordinates_t astro_get_body_coordinates(astro_body_t body, double et) {
astro_cartesian_coordinates_t retval = {0};
double coords[3];
switch(body) {
case ASTRO_BODY_SUN:
return retval; //Sun is at the center for vsop87a
case ASTRO_BODY_MERCURY:
vsop87a_milli_getMercury(et, coords);
break;
case ASTRO_BODY_VENUS:
vsop87a_milli_getVenus(et, coords);
break;
case ASTRO_BODY_EARTH:
vsop87a_milli_getEarth(et, coords);
break;
case ASTRO_BODY_MARS:
vsop87a_milli_getMars(et, coords);
break;
case ASTRO_BODY_JUPITER:
vsop87a_milli_getJupiter(et, coords);
break;
case ASTRO_BODY_SATURN:
vsop87a_milli_getSaturn(et, coords);
break;
case ASTRO_BODY_URANUS:
vsop87a_milli_getUranus(et, coords);
break;
case ASTRO_BODY_NEPTUNE:
vsop87a_milli_getNeptune(et, coords);
break;
case ASTRO_BODY_EMB:
vsop87a_milli_getEmb(et, coords);
break;
case ASTRO_BODY_MOON:
{
double earth_coords[3];
double emb_coords[3];
vsop87a_milli_getEarth(et, earth_coords);
vsop87a_milli_getEmb(et, emb_coords);
vsop87a_milli_getMoon(earth_coords, emb_coords, coords);
}
break;
}
retval.x = coords[0];
retval.y = coords[1];
retval.z = coords[2];
return retval;
}
astro_cartesian_coordinates_t astro_get_body_coordinates_light_time_adjusted(astro_body_t body, astro_cartesian_coordinates_t origin, double t) {
//Get current position of body
astro_cartesian_coordinates_t body_coords = astro_get_body_coordinates(body, t);
double newT = t;
for(uint8_t i = 0 ; i < 2 ; i++) {
//Calculate light time to body
body_coords = astro_subtract_cartesian(body_coords, origin);
double distance = sqrt(body_coords.x*body_coords.x + body_coords.y*body_coords.y + body_coords.z*body_coords.z);
distance *= 1.496e+11; //Convert from AU to meters
double lightTime = distance / 299792458.0;
//Convert light time to Julian Millenia, and subtract it from the original value of t
newT -= lightTime / 24.0 / 60.0 / 60.0 / 365250.0;
//Recalculate body position adjusted for light time
body_coords = astro_get_body_coordinates(body, newT);
}
return body_coords;
}
astro_horizontal_coordinates_t astro_ra_dec_to_alt_az(double jd, double lat, double lon, double ra, double dec) {
double GMST = astro_get_GMST(jd) * M_PI/180.0 * 15.0;
double h = GMST + lon - ra;
double sina = sin(dec)*sin(lat) + cos(dec)*cos(h)*cos(lat);
double a = asin(sina);
double cosAz = (sin(dec)*cos(lat) - cos(dec)*cos(h)*sin(lat)) / cos(a);
double Az = acos(cosAz);
if(sin(h) > 0) Az = 2.0*M_PI - Az;
astro_horizontal_coordinates_t retval;
retval.altitude = a;
retval.azimuth = Az;
return retval;
}
double astro_degrees_to_radians(double degrees) {
return degrees * M_PI / 180;
}
double astro_radians_to_degrees(double radians) {
return radians * 180.0 / M_PI;
}
astro_angle_dms_t astro_radians_to_dms(double radians) {
astro_angle_dms_t retval;
int8_t sign = (radians < 0) ? -1 : 1;
double degrees = fabs(astro_radians_to_degrees(radians));
retval.degrees = (uint16_t)degrees;
double temp = 60.0 * (degrees - retval.degrees);
retval.minutes = (uint8_t)temp;
retval.seconds = (uint8_t)round(60.0 * (temp - retval.minutes));
if (retval.seconds > 59) {
retval.seconds = 0.0;
retval.minutes++;
}
if (retval.minutes > 59) {
retval.minutes = 0;
retval.degrees++;
}
degrees *= sign;
return retval;
}
astro_angle_hms_t astro_radians_to_hms(double radians) {
astro_angle_hms_t retval;
double degrees = astro_radians_to_degrees(radians);
double temp = degrees / 15.0;
retval.hours = (uint8_t)temp;
temp = 60.0 * (temp - retval.hours);
retval.minutes = (uint8_t)temp;
retval.seconds = (uint8_t)round(60.0 * (temp - retval.minutes));
if (retval.seconds > 59) {
retval.seconds = 0;
retval.minutes++;
}
if (retval.minutes > 59) {
retval.minutes = 0;
retval.hours++;
}
return retval;
}

View File

@@ -0,0 +1,87 @@
/*
* Partial C port of Greg Miller's public domain astro library (gmiller@gregmiller.net) 2019
* https://github.com/gmiller123456/astrogreg
*
* Ported by Joey Castillo for Sensor Watch
* https://github.com/joeycastillo/Sensor-Watch/
*
* Public Domain
*
* THIS 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 ASTROLIB_H_
#define ASTROLIB_H_
typedef enum {
ASTRO_BODY_SUN = 0,
ASTRO_BODY_MERCURY,
ASTRO_BODY_VENUS,
ASTRO_BODY_EARTH,
ASTRO_BODY_MARS,
ASTRO_BODY_JUPITER,
ASTRO_BODY_SATURN,
ASTRO_BODY_URANUS,
ASTRO_BODY_NEPTUNE,
ASTRO_BODY_EMB,
ASTRO_BODY_MOON
} astro_body_t;
typedef struct {
double elements[3][3];
} astro_matrix_t;
typedef struct {
double x;
double y;
double z;
} astro_cartesian_coordinates_t;
typedef struct {
double right_ascension;
double declination;
double distance;
} astro_equatorial_coordinates_t;
typedef struct {
double altitude;
double azimuth;
} astro_horizontal_coordinates_t;
typedef struct {
int16_t degrees;
uint8_t minutes;
uint8_t seconds; // you may want this to be a float, watch just can't display any more digits
} astro_angle_dms_t;
typedef struct {
uint8_t hours;
uint8_t minutes;
uint8_t seconds; // you may want this to be a float, watch just can't display any more digits
} astro_angle_hms_t;
// Convert a date to a julian date. Must be in UTC+0 time zone!
double astro_convert_date_to_julian_date(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
// Converts a Julan Date to Julian Millenia since J2000, which is what VSOP87 expects as input.
double astro_convert_jd_to_julian_millenia_since_j2000(double jd);
// Get right ascension / declination for a given body in the list above.
astro_equatorial_coordinates_t astro_get_ra_dec(double jd, astro_body_t bodyNum, double lat, double lon, bool calculate_precession);
// Convert right ascension / declination to altitude/azimuth for a given location.
astro_horizontal_coordinates_t astro_ra_dec_to_alt_az(double jd, double lat, double lon, double ra, double dec);
// these are self-explanatory
double astro_degrees_to_radians(double degrees);
double astro_radians_to_degrees(double radians);
astro_angle_dms_t astro_radians_to_dms(double radians);
astro_angle_hms_t astro_radians_to_hms(double radians);
#endif // ASTROLIB_H_

221
legacy/lib/base32/base32.c Normal file
View File

@@ -0,0 +1,221 @@
/**
* base32 (de)coder implementation as specified by RFC4648.
*
* Copyright (c) 2010 Adrien Kunysz
*
* 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 <assert.h> // assert()
#include <limits.h> // CHAR_BIT
#include "base32.h"
/**
* Let this be a sequence of plain data before encoding:
*
* 01234567 01234567 01234567 01234567 01234567
* +--------+--------+--------+--------+--------+
* |< 0 >< 1| >< 2 ><|.3 >< 4.|>< 5 ><.|6 >< 7 >|
* +--------+--------+--------+--------+--------+
*
* There are 5 octets of 8 bits each in each sequence.
* There are 8 blocks of 5 bits each in each sequence.
*
* You probably want to refer to that graph when reading the algorithms in this
* file. We use "octet" instead of "byte" intentionnaly as we really work with
* 8 bits quantities. This implementation will probably not work properly on
* systems that don't have exactly 8 bits per (unsigned) char.
**/
static size_t min(size_t x, size_t y)
{
return x < y ? x : y;
}
static const unsigned char PADDING_CHAR = '=';
/**
* Pad the given buffer with len padding characters.
*/
static void pad(unsigned char *buf, int len)
{
for (int i = 0; i < len; i++)
buf[i] = PADDING_CHAR;
}
/**
* This convert a 5 bits value into a base32 character.
* Only the 5 least significant bits are used.
*/
static unsigned char encode_char(unsigned char c)
{
static unsigned char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
return base32[c & 0x1F]; // 0001 1111
}
/**
* Decode given character into a 5 bits value.
* Returns -1 iff the argument given was an invalid base32 character
* or a padding character.
*/
static int decode_char(unsigned char c)
{
int retval = -1;
if (c >= 'A' && c <= 'Z')
retval = c - 'A';
if (c >= '2' && c <= '7')
retval = c - '2' + 26;
assert(retval == -1 || ((retval & 0x1F) == retval));
return retval;
}
/**
* Given a block id between 0 and 7 inclusive, this will return the index of
* the octet in which this block starts. For example, given 3 it will return 1
* because block 3 starts in octet 1:
*
* +--------+--------+
* | ......<|.3 >....|
* +--------+--------+
* octet 1 | octet 2
*/
static int get_octet(int block)
{
assert(block >= 0 && block < 8);
return (block*5) / 8;
}
/**
* Given a block id between 0 and 7 inclusive, this will return how many bits
* we can drop at the end of the octet in which this block starts.
* For example, given block 0 it will return 3 because there are 3 bits
* we don't care about at the end:
*
* +--------+-
* |< 0 >...|
* +--------+-
*
* Given block 1, it will return -2 because there
* are actually two bits missing to have a complete block:
*
* +--------+-
* |.....< 1|..
* +--------+-
**/
static int get_offset(int block)
{
assert(block >= 0 && block < 8);
return (8 - 5 - (5*block) % 8);
}
/**
* Like "b >> offset" but it will do the right thing with negative offset.
* We need this as bitwise shifting by a negative offset is undefined
* behavior.
*/
static unsigned char shift_right(unsigned char byte, int offset)
{
if (offset > 0)
return byte >> offset;
else
return byte << -offset;
}
static unsigned char shift_left(unsigned char byte, int offset)
{
return shift_right(byte, - offset);
}
/**
* Encode a sequence. A sequence is no longer than 5 octets by definition.
* Thus passing a length greater than 5 to this function is an error. Encoding
* sequences shorter than 5 octets is supported and padding will be added to the
* output as per the specification.
*/
static void encode_sequence(const unsigned char *plain, int len, unsigned char *coded)
{
assert(CHAR_BIT == 8); // not sure this would work otherwise
assert(len >= 0 && len <= 5);
for (int block = 0; block < 8; block++) {
int octet = get_octet(block); // figure out which octet this block starts in
int junk = get_offset(block); // how many bits do we drop from this octet?
if (octet >= len) { // we hit the end of the buffer
pad(&coded[block], 8 - block);
return;
}
unsigned char c = shift_right(plain[octet], junk); // first part
if (junk < 0 // is there a second part?
&& octet < len - 1) // is there still something to read?
{
c |= shift_right(plain[octet+1], 8 + junk);
}
coded[block] = encode_char(c);
}
}
void base32_encode(const unsigned char *plain, size_t len, unsigned char *coded)
{
// All the hard work is done in encode_sequence(),
// here we just need to feed it the data sequence by sequence.
for (size_t i = 0, j = 0; i < len; i += 5, j += 8) {
encode_sequence(&plain[i], min(len - i, 5), &coded[j]);
}
}
static int decode_sequence(const unsigned char *coded, unsigned char *plain)
{
assert(CHAR_BIT == 8);
assert(coded && plain);
plain[0] = 0;
for (int block = 0; block < 8; block++) {
int offset = get_offset(block);
int octet = get_octet(block);
int c = decode_char(coded[block]);
if (c < 0) // invalid char, stop here
return octet;
plain[octet] |= shift_left(c, offset);
if (offset < 0) { // does this block overflows to next octet?
assert(octet < 4);
plain[octet+1] = shift_left(c, 8 + offset);
}
}
return 5;
}
size_t base32_decode(const unsigned char *coded, unsigned char *plain)
{
size_t written = 0;
for (size_t i = 0, j = 0; ; i += 8, j += 5) {
int n = decode_sequence(&coded[i], &plain[j]);
written += n;
if (n < 5)
return written;
}
}

View File

@@ -0,0 +1,66 @@
/**
* base32 (de)coder implementation as specified by RFC4648.
*
* Copyright (c) 2010 Adrien Kunysz
*
* 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 __BASE32_H_
#define __BASE32_H_
#include <stddef.h> // size_t
/**
* Returns the length of the output buffer required to encode len bytes of
* data into base32. This is a macro to allow users to define buffer size at
* compilation time.
*/
#define BASE32_LEN(len) (((len)/5)*8 + ((len) % 5 ? 8 : 0))
/**
* Returns the length of the output buffer required to decode a base32 string
* of len characters. Please note that len must be a multiple of 8 as per
* definition of a base32 string. This is a macro to allow users to define
* buffer size at compilation time.
*/
#define UNBASE32_LEN(len) (((len)/8)*5)
/**
* Encode the data pointed to by plain into base32 and store the
* result at the address pointed to by coded. The "coded" argument
* must point to a location that has enough available space
* to store the whole coded string. The resulting string will only
* contain characters from the [A-Z2-7=] set. The "len" arguments
* define how many bytes will be read from the "plain" buffer.
**/
void base32_encode(const unsigned char *plain, size_t len, unsigned char *coded);
/**
* Decode the null terminated string pointed to by coded and write
* the decoded data into the location pointed to by plain. The
* "plain" argument must point to a location that has enough available
* space to store the whole decoded string.
* Returns the length of the decoded string. This may be less than
* expected due to padding. If an invalid base32 character is found
* in the coded string, decoding will stop at that point.
**/
size_t base32_decode(const unsigned char *coded, unsigned char *plain);
#endif

113
legacy/lib/morsecalc/calc.c Normal file
View File

@@ -0,0 +1,113 @@
/*
* MIT License
*
* Copyright (c) 2023 Christian Chapman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "calc.h"
#include "calc_fns.h"
#define CALC_NAN (0.0/0.0)
/* calc_init
* Initialize calculator
*/
int calc_init(calc_state_t *cs) {
memset(cs->stack, CALC_NAN, N_STACK*sizeof(cs->stack[0]));
cs->s = 0;
cs->mem = 0.0;
return 0;
}
/* calc_input_function
* Try to 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<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
}
}
}
return -1; // Unrecognized function name
}
/* calc_input_float
* Read the token as a float.
* For convenience, numerals can be written in binary:
* 0 1 2 3 4 5 6 7 8 9
* . - -. -- -.. -.- --. --- -... -..-
* e t n m d k g o b x
*
* Exponent signs must be entered as "p".
* Decimal place "." can be entered as "h" (code ....)
* Sign "-" can be entered as "Ch digraph" (code ----)
*
* e.g. "4.2e-3" can be entered directly or as "4h2pC3"
* similarly, "0.0042" can be "eheedn"
*/
#define REPCHAR(X,Y) for(idx=0; idx<strlen(token); idx++) \
if(X==token[idx]) token[idx] = Y
int calc_input_float(calc_state_t *cs, char *token) {
uint8_t idx;
REPCHAR('e', '0');
REPCHAR('t', '1');
REPCHAR('n', '2');
REPCHAR('m', '3');
REPCHAR('d', '4');
REPCHAR('k', '5');
REPCHAR('g', '6');
REPCHAR('o', '7');
REPCHAR('b', '8');
REPCHAR('x', '9');
REPCHAR('h', '.');
REPCHAR('C', '-');
REPCHAR('p', 'E');
char *endptr;
double d = calc_strtof(token, &endptr);
if(!endptr || (uint8_t)(endptr-token)<strlen(token)) return -1; // Bad format
if(cs->s >= N_STACK) return -2; // Stack full
cs->stack[cs->s++] = d;
return 0;
}
/* calc_input
* Manipulate the stack using the entered token.
* If the token isn't a calculator function, try to convert it to a number and
* add it to the stack.
*
* Return values:
* 0 if function completed successfully.
* -1 if token isn't a calculator function and couldn't convert to float.
* -2 if stack is too full or too empty
* -3 for something else
*/
int calc_input(calc_state_t *cs, char *token) {
int retval = calc_input_function(cs, token);
if(-1 == retval) retval = calc_input_float(cs, token);
return retval;
}

View File

@@ -0,0 +1,44 @@
/*
* MIT License
*
* Copyright (c) 2023 Christian Chapman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CALC_H_INCLUDED
#define CALC_H_INCLUDED
#include <stdint.h>
#define N_STACK 10
typedef struct {
double stack[N_STACK];
double mem;
uint8_t s; // # of items in stack
} calc_state_t;
int calc_init(calc_state_t *cs);
int calc_input(calc_state_t *cs, char *token);
int calc_input_function(calc_state_t *cs, char *token);
int calc_input_float(calc_state_t *cs, char *token);
double calc_strtof(const char *str, char **endptr);
#endif

View File

@@ -0,0 +1,240 @@
/*
* MIT License
*
* Copyright (c) 2023 Christian Chapman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <string.h>
#include <math.h>
#include "calc_fns.h"
#define STACK_CHECK_0_IN_1_OUT if(cs->s >= N_STACK) return -2
#define STACK_CHECK_1_IN_0_OUT if(cs->s < 1) return -2
#define STACK_CHECK_1_IN_1_OUT if(cs->s < 1) return -2
#define STACK_CHECK_2_IN_1_OUT if(cs->s < 2) return -2
#define STACK_CHECK_2_IN_2_OUT if(cs->s < 2) return -2
static const double to_rad = M_PI/180;
static const double to_deg = 180/M_PI;
// Stack and memory control
int calc_delete(calc_state_t *cs) {
if(cs->s < 1) return -2; // Check stack
cs->s--;
return 0;
}
int calc_clear_stack(calc_state_t *cs) {
memset(cs->stack, (0.0/0.0), N_STACK*sizeof(cs->stack[0]));
cs->s = 0;
return 0;
}
int calc_flip(calc_state_t *cs) {
STACK_CHECK_2_IN_2_OUT;
double buff = cs->stack[cs->s-2];
cs->stack[cs->s-2] = cs->stack[cs->s-1];
cs->stack[cs->s-1] = buff;
return 0;
}
int calc_mem_clear(calc_state_t *cs) {
cs->mem = 0.0;
return 0;
}
int calc_mem_recall(calc_state_t *cs) {
STACK_CHECK_0_IN_1_OUT;
cs->stack[cs->s++] = cs->mem;
return 0;
}
int calc_mem_add(calc_state_t *cs) {
STACK_CHECK_1_IN_0_OUT;
cs->mem += cs->stack[cs->s-1];
cs->s--;
return 0;
}
int calc_mem_subtract(calc_state_t *cs) {
STACK_CHECK_1_IN_0_OUT;
cs->mem -= cs->stack[cs->s-1];
cs->s--;
return 0;
}
// Basic operations
int calc_add(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] += cs->stack[cs->s-1];
cs->s--;
return 0;
}
int calc_subtract(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] -= cs->stack[cs->s-1];
cs->s--;
return 0;
}
int calc_negate(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = -cs->stack[cs->s-1];
return 0;
}
int calc_multiply(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] *= cs->stack[cs->s-1];
cs->s--;
return 0;
}
int calc_divide(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] /= cs->stack[cs->s-1];
cs->s--;
return 0;
}
int calc_invert(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = 1.0/cs->stack[cs->s-1];
return 0;
}
// Constants
int calc_e(calc_state_t *cs) {
STACK_CHECK_0_IN_1_OUT;
cs->stack[cs->s++] = M_E;
return 0;
}
int calc_pi(calc_state_t *cs) {
STACK_CHECK_0_IN_1_OUT;
cs->stack[cs->s++] = M_PI;
return 0;
}
// Exponential/logarithmic
int calc_exp(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = exp(cs->stack[cs->s-1]);
return 0;
}
int calc_pow(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] = pow(cs->stack[cs->s-2], cs->stack[cs->s-1]);
cs->s--;
return 0;
}
int calc_ln(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = log(cs->stack[cs->s-1]);
return 0;
}
int calc_log(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = log10(cs->stack[cs->s-1]);
return 0;
}
int calc_sqrt(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = sqrt(cs->stack[cs->s-1]);
return 0;
}
// Trigonometric
int calc_sin(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = sin(cs->stack[cs->s-1]);
return 0;
}
int calc_cos(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = cos(cs->stack[cs->s-1]);
return 0;
}
int calc_tan(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = tan(cs->stack[cs->s-1]);
return 0;
}
int calc_asin(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = asin(cs->stack[cs->s-1]);
return 0;
}
int calc_acos(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = acos(cs->stack[cs->s-1]);
return 0;
}
int calc_atan(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = atan(cs->stack[cs->s-1]);
return 0;
}
int calc_atan2(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] = atan2(cs->stack[cs->s-2], cs->stack[cs->s-1]);
cs->s--;
return 0;
}
int calc_sind(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = sin(cs->stack[cs->s-1]*to_rad);
return 0;
}
int calc_cosd(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = cos(cs->stack[cs->s-1]*to_rad);
return 0;
}
int calc_tand(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = tan(cs->stack[cs->s-1]*to_rad);
return 0;
}
int calc_asind(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = asin(cs->stack[cs->s-1])*to_deg;
return 0;
}
int calc_acosd(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = acos(cs->stack[cs->s-1])*to_deg;
return 0;
}
int calc_atand(calc_state_t *cs) {
STACK_CHECK_1_IN_1_OUT;
cs->stack[cs->s-1] = atan(cs->stack[cs->s-1])*to_deg;
return 0;
}
int calc_atan2d(calc_state_t *cs) {
STACK_CHECK_2_IN_1_OUT;
cs->stack[cs->s-2] = atan2(cs->stack[cs->s-2], cs->stack[cs->s-1])*to_deg;
cs->s--;
return 0;
}
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;
}

View File

@@ -0,0 +1,128 @@
/*
* MIT License
*
* Copyright (c) 2023 Christian Chapman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "calc.h"
// Stack and register control
int calc_delete(calc_state_t *cs);
int calc_clear_stack(calc_state_t *cs);
int calc_flip(calc_state_t *cs);
int calc_mem_clear(calc_state_t *cs);
int calc_mem_recall(calc_state_t *cs);
int calc_mem_add(calc_state_t *cs);
int calc_mem_subtract(calc_state_t *cs);
// Basic operations
int calc_add(calc_state_t *cs);
int calc_subtract(calc_state_t *cs);
int calc_negate(calc_state_t *cs);
int calc_multiply(calc_state_t *cs);
int calc_divide(calc_state_t *cs);
int calc_invert(calc_state_t *cs);
// Constants
int calc_e(calc_state_t *cs);
int calc_pi(calc_state_t *cs);
// Exponential/logarithmic
int calc_exp(calc_state_t *cs);
int calc_pow(calc_state_t *cs);
int calc_ln(calc_state_t *cs);
int calc_log(calc_state_t *cs);
int calc_sqrt(calc_state_t *cs);
// Trigonometric
int calc_sin(calc_state_t *cs);
int calc_cos(calc_state_t *cs);
int calc_tan(calc_state_t *cs);
int calc_asin(calc_state_t *cs);
int calc_acos(calc_state_t *cs);
int calc_atan(calc_state_t *cs);
int calc_atan2(calc_state_t *cs);
int calc_sind(calc_state_t *cs);
int calc_cosd(calc_state_t *cs);
int calc_tand(calc_state_t *cs);
int calc_asind(calc_state_t *cs);
int calc_acosd(calc_state_t *cs);
int calc_atand(calc_state_t *cs);
int calc_atan2d(calc_state_t *cs);
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 {
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
{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
{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
{1, (const char*[]){"e"}, &calc_e},
{1, (const char*[]){"pi"}, &calc_pi},
// Exponential/logarithmic
{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
{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},
};

View File

@@ -0,0 +1,144 @@
//
// strtod.c
//
// Convert string to double
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <stdlib.h>
#include "calc.h"
double calc_strtof(const char *str, char **endptr) {
double number;
int exponent;
int negative;
char *p = (char *) str;
double p10;
int n;
int num_digits;
int num_decimals;
// Skip leading whitespace
while (isspace((int) *p)) p++;
// Handle optional sign
negative = 0;
switch (*p) {
case '-': negative = 1; // Fall through to increment position
__attribute__ ((fallthrough));
case '+': p++;
}
number = 0.;
exponent = 0;
num_digits = 0;
num_decimals = 0;
// Process string of digits
while (isdigit((int) *p)) {
number = number * 10. + (*p - '0');
p++;
num_digits++;
}
// Process decimal part
if (*p == '.') {
p++;
while (isdigit((int) *p)) {
number = number * 10. + (*p - '0');
p++;
num_digits++;
num_decimals++;
}
exponent -= num_decimals;
}
if (num_digits == 0) {
if (endptr) *endptr = p;
return 0.0;
}
// Correct for sign
if (negative) number = -number;
// Process an exponent string
if (*p == 'e' || *p == 'E') {
// Handle optional sign
negative = 0;
switch (*++p) {
case '-': negative = 1; // Fall through to increment pos
__attribute__ ((fallthrough));
case '+': p++;
}
// Process string of digits
n = 0;
while (isdigit((int) *p)) {
n = n * 10 + (*p - '0');
p++;
}
if (negative) {
exponent -= n;
} else {
exponent += n;
}
}
if (exponent < DBL_MIN_EXP || exponent > DBL_MAX_EXP) {
return HUGE_VAL;
}
// Scale the result
p10 = 10.;
n = exponent;
if (n < 0) n = -n;
while (n) {
if (n & 1) {
if (exponent < 0) {
number /= p10;
} else {
number *= p10;
}
}
n >>= 1;
p10 *= p10;
}
if (endptr) *endptr = p;
return number;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,35 @@
/*
* 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 "morsecalc_face.h"
// Display float on screen
void morsecalc_display_float(double d);
// Print current input token
void morsecalc_display_token(morsecalc_state_t *mcs);
// Print stack or memory register contents.
void morsecalc_display_stack(morsecalc_state_t *mcs);

View File

@@ -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;
}

File diff suppressed because it is too large Load Diff

5
legacy/lib/vsop87/LICENSE.txt Executable file
View File

@@ -0,0 +1,5 @@
Author: Greg A Miller (gmiller@gregmiller.net)
I release all of my work in this project to the public domain.
Note, though, not everything here can be considered my work. The VSOP87 theory is the work of
Bureau des Longitudes.

143
legacy/lib/vsop87/README.md Executable file
View File

@@ -0,0 +1,143 @@
# Example
An example in JavaScript showing computations is available at http://www.celestialprogramming.com/vsop87-multilang/
### Current Stauts
Versions for many different languages and environments have been created. These have passed all test cases provided by the
VSOP87 authors, a validation program is also included for each language. They are ready for use. See the "Languages" directory for the status
of each language.
Still to come: more languages, documentation, and examples.
# About this Project
The purpose of this project is to create versions of VSOP87 in many different languages. The plan is to generate a initial version in C# truncated to different
precisions, then convert the resulting files into other languages. This project was inspired by the [Neoprogrammics Source Code Generator Tool](http://www.neoprogrammics.com/vsop87/source_code_generator_tool/).
The goal of this project is to provide easier to use (readymade source files), include all data (e.g. the Moon and velocities), are truncated to different accuracy levels,
, for more languages, and have a more permissive license (public domain). It will provide tests against the original VSOP87 test data for each programming language to validate correctness, as well as examples to show common useage.
# Language Status
Below is a list of the planned languages and their current status.
Language |Inline|JSON|CSV|CSV Low Mem|Validation Tests|Alt Az Reduction Example
-------------|------|----|---|-----------|----------------|--------------
Java |Yes | |Yes| |Pass |
C |Yes | | |Yes |Pass |
C# |Yes | |Yes| |Pass |
Python |Yes |Yes |Yes| |Pass |
C++ |Yes | | | |Pass |
VB.Net |Yes | | | |Pass |
JavaScript |Yes |Yes | | |Pass |Yes
PHP |Yes | | | |Pass |
Ruby |Yes | | | |Pass |
Swift |Yes | | | |Pass |
Matlab/Octave|Yes | | | |Pass |
Groovy | | |Yes| |Pass |
Go |Yes | | | |Pass |
Pascal |Yes | | | |0.000009au |
Perl |Yes | | |Yes |Pass |
R |Fail! | | | |Fail! |
Cobol | | | | | |
Fortran | | | | | |
Rust |Yes | | | |Pass |
Arduino |Yes | | | |0.000009au |
# What is VSOP87? Why use it?
VSOP87 is one of many solutions available for predicting the positions of the planets (plus the Sun and the Moon) in our solar system. The actual name is
Variations S<>culaires des Orbites Plan<61>taires, and it was published in 1987 by the Bureau des Longitudes. Even though there have been many other methods
developed before and after VSOP87, it remains one of the most popular methods among amatuers. It provides better accuracy than most amatuers require (.1 arcseconds)
over a period of a few thousand years before and after the year 2000.
# Implementation Types
There are a few different types of implementations: Inline, JSON, CSV, and CSV Low Memory. The inline versions are generally the easiest to use as they will have no
external requirements, they are also the easiest to generate, so they're available for more languages. The JSON versions require a JSON file (located in the
languages/JSON folder) which is loaded into memory. The advantages of the JSON versions are you can compute the velocities with the same data the positions
are generated from, and you can load and dispose of the memory used by the data when you need it. The CSV implementations are similar to the JSON implementations,
but, obviously, read from a CSV file (located in the languages/CSV folder). And the Low Memory CSV implementations read the same CSV files, but the
data is not retained in memory. The JSON versions are located in the Languages/JSON directory, as well as the required JSON files, and the CSV implementations
are in the languages/CSV folder.
# Which Version Should I Use?
For the overwhelming majority of users, the VSOP87A_full version will be all that you need. This is the full version, but should still be fast enough and small enough
for most use cases. Using the full version eliminates any questions of whether it will be accurate enough. If, after trying the full version, the computation isn't
fast enough, from there you should experiment with truncated versions. The VSOP87A versions are the only versions which include both the Earth and Moon. VSOP87A doesn't include the moon directly, but does include the Earth and the Earth-Moon Barrycenter, and all provided code for the VSOP87A versions include a function to compute the Moon's position from the Earth and EMB. Using the versions that provide the velocities is necessary if you want to account for relativistic effects do to the motion of the observer.
There are several versions of the main theory. The first is just called VSOP87, the remainder of them are appended with the letters A, B, C, D, E. Each version
provides the data in a slightly different form.
Version|Mercury|Venus|Earth|EMB|Mars|Jupiter|Saturn|Uranus|Neptune|Sun|Coordinates
-------|-------|-----|-----|---|----|-------|------|------|-------|---|-----------
VSOP87|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|Yes|No|Keperian Orbital Elements
VSOP87A|Yes|Yes|Yes|Yes|Yes|Yes|Yes|Yes|Yes|No|Heliocentric J2000 Ecliptic Rectangular XYZ
VSOP87B|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric J2000 Ecliptic Spherical LBR
VSOP87C|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric Ecliptic of date Rectangular XYZ
VSOP87D|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|No|Heliocentric Ecliptic of date Spherical LBR
VSOP87E|Yes|Yes|Yes|No|Yes|Yes|Yes|Yes|Yes|Yes|Barycentric J2000 Ecliptic Rectangular XYZ
# Truncated versions
Since the full VSOP87 provides more accuracy than most amateurs require, the algorithm can be shortened by eliminating terms. This speeds up the computations, and
reduces the overall size of the code at the cost of accuracy. For each programming language, this project supplies VSOP87 truncated at ten different levels. The
effects of accuracy are detailed in the graphs below. Each level of truncation eliminates any terms with a coefficient 1/10 the previous truncation level.
Trunaction Level|Total Terms|Skipped Terms|Percent Skipped
----------------|-----------|-------------|---------------
full |269949|0 |0 %
xx large |269949|20998 |7.7 %
x large |269949|67848 |25.1 %
large |269949|145031|53.7 %
small |269949|218559|80.9 %
x small |269949|250204|92.6 %
milli |269949|262369|97.1 %
micro |269949|266975|98.8 %
nano |269949|268686|99.5 %
pico |269949|269464|99.8 %
# Accuracy
Accuracy graphs are below. They show the error in degrees of each body as viewed from Earth. Each graph shows the error for one body for all truncated versions of
VSOP87. The error is vs. the full version of VSOP87, so the inherent error in VSOP87 also has to be added. Some bodies appear twice, to zoom in on the lower portion
of the graph, as the error of the pico version makes it difficult to see errors amongst the larger versions. The Python script and data to reproduce the graphs is
in the Accuracy folder, by regenerating them you can use the Matplotlib interface to explore the graphs further.
Since the error is computed from the geocenter, the Earth does not appear in the graphs below, nor does the Sun. Graphs are also not present for the Moon, but graphs
are available for the Earth-Moon Barrycenter (EMB), the error for the Moon will be a linear function of the EMB error.
The full VSOP87 accuracy is .1 arcseconds for Saturn, and better for all others. For more details on accuracy of the full theory consult
[Planetary theories in rectangular and spherical variables - VSOP 87 solutions](http://articles.adsabs.harvard.edu/full/1988A%26A...202..309B).
### Mercury
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/mercury.png)
### Venus
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/venus.png)
### Earth-Moon Barrycenter
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/emb.png)
### Earth-Moon Barrycenter (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/emb2.png)
### Mars
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/mars.png)
### Jupiter
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/jupiter.png)
### Saturn
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/saturn.png)
### Saturn (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/saturn2.png)
### Uranus
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/uranus.png)
### Uranus (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/uranus2.png)
### Neptune
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/neptune.png)
### Neptune (zoomed)
![Mercury](https://raw.githubusercontent.com/gmiller123456/vsop87-multilang/master/utility/Accuracy/neptune2.png)

1019
legacy/lib/vsop87/vsop87a_micro.c Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
//VSOP87-Multilang http://www.astrogreg.com/vsop87-multilang/index.html
//Greg Miller (gmiller@gregmiller.net) 2019. Released as Public Domain
#ifndef VSOP87A_MICRO
#define VSOP87A_MICRO
void vsop87a_micro_getEarth(double t,double temp[]);
void vsop87a_micro_getEmb(double t,double temp[]);
void vsop87a_micro_getJupiter(double t,double temp[]);
void vsop87a_micro_getMars(double t,double temp[]);
void vsop87a_micro_getMercury(double t,double temp[]);
void vsop87a_micro_getNeptune(double t,double temp[]);
void vsop87a_micro_getSaturn(double t,double temp[]);
void vsop87a_micro_getUranus(double t,double temp[]);
void vsop87a_micro_getVenus(double t,double temp[]);
void vsop87a_micro_getMoon(double earth[], double emb[],double temp[]);
#endif

2048
legacy/lib/vsop87/vsop87a_milli.c Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
//VSOP87-Multilang http://www.astrogreg.com/vsop87-multilang/index.html
//Greg Miller (gmiller@gregmiller.net) 2019. Released as Public Domain
#ifndef VSOP87A_MILLI
#define VSOP87A_MILLI
void vsop87a_milli_getEarth(double t,double temp[]);
void vsop87a_milli_getEmb(double t,double temp[]);
void vsop87a_milli_getJupiter(double t,double temp[]);
void vsop87a_milli_getMars(double t,double temp[]);
void vsop87a_milli_getMercury(double t,double temp[]);
void vsop87a_milli_getNeptune(double t,double temp[]);
void vsop87a_milli_getSaturn(double t,double temp[]);
void vsop87a_milli_getUranus(double t,double temp[]);
void vsop87a_milli_getVenus(double t,double temp[]);
void vsop87a_milli_getMoon(double earth[], double emb[],double temp[]);
#endif

128
legacy/movement_faces.h Normal file
View File

@@ -0,0 +1,128 @@
/*
* MIT License
*
* 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 MOVEMENT_FACES_H_
#define MOVEMENT_FACES_H_
#include "simple_clock_face.h"
#include "close_enough_clock_face.h"
#include "clock_face.h"
#include "world_clock_face.h"
#include "preferences_face.h"
#include "set_time_face.h"
#include "set_time_hackwatch_face.h"
#include "pulsometer_face.h"
#include "temperature_display_face.h"
#include "temperature_logging_face.h"
#include "thermistor_testing_face.h"
#include "character_set_face.h"
#include "beats_face.h"
#include "day_one_face.h"
#include "voltage_face.h"
#include "stopwatch_face.h"
#include "totp_face.h"
#include "totp_face_lfs.h"
#include "lis2dw_logging_face.h"
#include "demo_face.h"
#include "hello_there_face.h"
#include "sunrise_sunset_face.h"
#include "countdown_face.h"
#include "sailing_face.h"
#include "counter_face.h"
#include "blinky_face.h"
#include "moon_phase_face.h"
#include "accelerometer_data_acquisition_face.h"
#include "mars_time_face.h"
#include "orrery_face.h"
#include "astronomy_face.h"
#include "tomato_face.h"
#include "probability_face.h"
#include "wake_face.h"
#include "frequency_correction_face.h"
#include "alarm_face.h"
#include "ratemeter_face.h"
#include "rpn_calculator_alt_face.h"
#include "weeknumber_clock_face.h"
#include "fast_stopwatch_face.h"
#include "tachymeter_face.h"
#include "nanosec_face.h"
#include "finetune_face.h"
#include "databank_face.h"
#include "tempchart_face.h"
#include "tally_face.h"
#include "tarot_face.h"
#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"
#include "simple_clock_bin_led_face.h"
#include "menstrual_cycle_face.h"
#include "flashlight_face.h"
#include "decimal_time_face.h"
#include "wyoscan_face.h"
#include "save_load_face.h"
#include "day_night_percentage_face.h"
#include "simple_coin_flip_face.h"
#include "solstice_face.h"
#include "couch_to_5k_face.h"
#include "minute_repeater_decimal_face.h"
#include "tuning_tones_face.h"
#include "minmax_face.h"
#include "kitchen_conversions_face.h"
#include "butterfly_game_face.h"
#include "wareki_face.h"
#include "wordle_face.h"
#include "endless_runner_face.h"
#include "periodic_face.h"
#include "deadline_face.h"
#include "higher_lower_game_face.h"
#include "french_revolutionary_face.h"
#include "minimal_clock_face.h"
#include "simon_face.h"
#include "simple_calculator_face.h"
#include "alarm_thermometer_face.h"
#include "beeps_face.h"
#include "accel_interrupt_count_face.h"
#include "metronome_face.h"
#include "smallchess_face.h"
// New includes go above this line.
#endif // MOVEMENT_FACES_H_

View File

@@ -0,0 +1,231 @@
/*
* MIT License
*
* Copyright (c) 2024 Ruben Nic
*
* 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 "close_enough_clock_face.h"
#include "watch.h"
#include "watch_utility.h"
const char *words[12] = {
" ",
" 5",
"10",
"15",
"20",
"25",
"30",
"35",
"40",
"45",
"50",
"55",
};
static const char *past_word = " P";
static const char *to_word = " 2";
static const char *oclock_word = "OC";
// sets when in the five minute period we switch
// from "X past HH" to "X to HH+1"
static const int hour_switch_index = 8;
static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) {
state->alarm_enabled = settings_alarm_enabled;
if (state->alarm_enabled) {
watch_set_indicator(WATCH_INDICATOR_BELL);
} else {
watch_clear_indicator(WATCH_INDICATOR_BELL);
};
}
void close_enough_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(close_enough_clock_state_t));
}
}
void close_enough_clock_face_activate(void *context) {
close_enough_clock_state_t *state = (close_enough_clock_state_t *)context;
if (watch_sleep_animation_is_running()) {
watch_stop_sleep_animation();
}
if (movement_clock_mode_24h()) {
watch_set_indicator(WATCH_INDICATOR_24H);
}
// show alarm indicator if there is an active alarm
_update_alarm_indicator(movement_alarm_enabled(), state);
// this ensures that none of the five_minute_periods will match, so we always rerender when the face activates
state->prev_five_minute_period = -1;
state->prev_min_checked = -1;
}
bool close_enough_clock_face_loop(movement_event_t event, void *context) {
close_enough_clock_state_t *state = (close_enough_clock_state_t *)context;
char buf[11];
watch_date_time_t date_time;
bool show_next_hour = false;
int prev_five_minute_period;
int prev_min_checked;
int close_enough_hour;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
prev_five_minute_period = state->prev_five_minute_period;
prev_min_checked = state->prev_min_checked;
// 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);
}
// same minute, skip update
if (date_time.unit.minute == prev_min_checked) {
break;
} else {
state->prev_min_checked = date_time.unit.minute;
}
int five_minute_period = (date_time.unit.minute / 5) % 12;
// If we are 60% to the next 5 interval, move up to the next period
if (fmodf(date_time.unit.minute / 5.0f, 1.0f) > 0.5f) {
// If we are on the last 5 interval and moving to the next period we need to display the next hour because we are wrapping around
if (five_minute_period == 11) {
show_next_hour = true;
}
five_minute_period = (five_minute_period + 1) % 12;
}
// same five_minute_period, skip update
if (five_minute_period == prev_five_minute_period) {
break;
}
// we don't want to modify date_time.unit.hour just in case other watch faces use it
close_enough_hour = date_time.unit.hour;
// move from "MM(mins) P HH" to "MM(mins) 2 HH+1"
if (five_minute_period >= hour_switch_index || show_next_hour) {
close_enough_hour = (close_enough_hour + 1) % 24;
}
if (!movement_clock_mode_24h()) {
// if we are in 12 hour mode, do some cleanup.
if (close_enough_hour < 12) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
close_enough_hour %= 12;
if (close_enough_hour == 0) {
close_enough_hour = 12;
}
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) {
date_time.unit.hour = 12;
}
}
char first_word[3];
char second_word[3];
char third_word[3];
if (five_minute_period == 0) { // "HH OC",
sprintf(first_word, "%2d", close_enough_hour);
strncpy(second_word, words[five_minute_period], 3);
strncpy(third_word, oclock_word, 3);
} else {
int words_length = sizeof(words) / sizeof(words[0]);
strncpy(
first_word,
five_minute_period >= hour_switch_index ?
words[words_length - five_minute_period] :
words[five_minute_period],
3
);
strncpy(
second_word,
five_minute_period >= hour_switch_index ?
to_word : past_word,
3
);
sprintf(third_word, "%2d", close_enough_hour);
}
sprintf(
buf,
"%s%2d%s%s%s",
watch_utility_get_weekday(date_time),
date_time.unit.day,
first_word,
second_word,
third_word
);
watch_display_string(buf, 0);
state->prev_five_minute_period = five_minute_period;
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) {
_update_alarm_indicator(movement_alarm_enabled(), state);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void close_enough_clock_face_resign(void *context) {
(void) context;
}

View File

@@ -0,0 +1,62 @@
/*
* MIT License
*
* Copyright (c) 2024 Ruben Nic
*
* 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 CLOSE_ENOUGH_CLOCK_FACE_H_
#define CLOSE_ENOUGH_CLOCK_FACE_H_
/*
* CLOSE ENOUGH CLOCK FACE
*
* Displays the current time; but only in periods of 5.
* Just in the in the formats of:
* - "10 past 5"
* - "15 to 7"
* - "6 o'clock"
*
*/
#include "movement.h"
typedef struct {
int prev_five_minute_period;
int prev_min_checked;
uint8_t last_battery_check;
bool battery_low;
bool alarm_enabled;
} close_enough_clock_state_t;
void close_enough_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void close_enough_clock_face_activate(void *context);
bool close_enough_clock_face_loop(movement_event_t event, void *context);
void close_enough_clock_face_resign(void *context);
#define close_enough_clock_face ((const watch_face_t){ \
close_enough_clock_face_setup, \
close_enough_clock_face_activate, \
close_enough_clock_face_loop, \
close_enough_clock_face_resign, \
NULL, \
})
#endif // CLOSE_ENOUGH_CLOCK_FACE_H_

View File

@@ -0,0 +1,136 @@
/*
* MIT License
*
* Copyright (c) 2023 Wesley Aptekar-Cassels
*
* 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 "day_night_percentage_face.h"
#include "watch_utility.h"
#include "sunriset.h"
// fmod but handle negatives right
static double better_fmod(double x, double y) {
return fmod(fmod(x, y) + y, y);
}
static void recalculate(watch_date_time_t utc_now, day_night_percentage_state_t *state) {
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
if (movement_location.reg == 0) {
state->result = -2;
return;
}
// Weird quirky unsigned things were happening when I tried to cast these directly to doubles below.
// it looks redundant, but extracting them to local int16's seemed to fix it.
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;
state->daylen = day_length(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat);
state->result = sun_rise_set(utc_now.unit.year + WATCH_RTC_REFERENCE_YEAR, utc_now.unit.month, utc_now.unit.day, lon, lat, &state->rise, &state->set);
}
void day_night_percentage_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(day_night_percentage_state_t));
day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr;
watch_date_time_t utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_get_current_timezone_offset(), 0);
recalculate(utc_now, state);
}
}
void day_night_percentage_face_activate(void *context) {
(void) context;
}
bool day_night_percentage_face_loop(movement_event_t event, void *context) {
day_night_percentage_state_t *state = (day_night_percentage_state_t *)context;
char buf[12];
watch_date_time_t date_time = watch_rtc_get_date_time();
watch_date_time_t utc_now = watch_utility_date_time_convert_zone(date_time, movement_get_current_timezone_offset(), 0);
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
if ((utc_now.unit.hour == 0 && utc_now.unit.minute == 0 && utc_now.unit.second == 0) || state->result == -2) {
recalculate(utc_now, state);
}
if (state->result == -2) {
watch_display_string(" no Loc", 0);
break;
}
const char* weekday = watch_utility_get_weekday(date_time);
if (state->result != 0) {
if (state->result == 1) {
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
watch_set_indicator(WATCH_INDICATOR_PM);
}
sprintf(buf, "%s%2dEtrnal", weekday, date_time.unit.day);
watch_display_string(buf, 0);
} else {
double day_hours_decimal = utc_now.unit.hour + (utc_now.unit.minute + (utc_now.unit.second / 60.0)) / 60.0;
double day_percentage = (24.0 - better_fmod(state->rise - day_hours_decimal, 24.0)) / state->daylen;
double night_percentage = (24.0 - better_fmod(state->set - day_hours_decimal, 24.0)) / (24 - state->daylen);
uint16_t percentage;
if (day_percentage > 0.0 && day_percentage < 1.0) {
percentage = day_percentage * 10000;
watch_clear_indicator(WATCH_INDICATOR_PM);
} else {
percentage = night_percentage * 10000;
watch_set_indicator(WATCH_INDICATOR_PM);
}
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
if (!watch_sleep_animation_is_running()) watch_start_sleep_animation(500);
sprintf(buf, "%s%2d %02d ", weekday, date_time.unit.day, percentage / 100);
} else {
sprintf(buf, "%s%2d %04d", weekday, date_time.unit.day, percentage);
}
watch_display_string(buf, 0);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void day_night_percentage_face_resign(void *context) {
(void) context;
}

View File

@@ -0,0 +1,66 @@
/*
* MIT License
*
* Copyright (c) 2023 Wesley Aptekar-Cassels
*
* 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 DAY_NIGHT_PERCENTAGE_FACE_H_
#define DAY_NIGHT_PERCENTAGE_FACE_H_
#include "movement.h"
/*
* Day/night percentage face
*
* Shows the percentage of the way through the day/night the current time is.
*
* The time digits show the percentage of the way through the day/night it is,
* with decimals in the smaller seconds digits. If the day or night will last
* for a full 24 hours, the text "Etrnal" is displayed instead of a percentage.
* The "PM" indicator is set when it is currently nighttime. The weekday and
* day digits display the weekday and day, as one would expect.
*
* This face does not currently offer any configuration. You must set the
* location register with some other face.
*/
typedef struct {
int result; // -1, 0, 1: result from sun_rise_set, -2: no location set
double rise;
double set;
double daylen;
} day_night_percentage_state_t;
void day_night_percentage_face_setup(uint8_t watch_face_index, void ** context_ptr);
void day_night_percentage_face_activate(void *context);
bool day_night_percentage_face_loop(movement_event_t event, void *context);
void day_night_percentage_face_resign(void *context);
#define day_night_percentage_face ((const watch_face_t){ \
day_night_percentage_face_setup, \
day_night_percentage_face_activate, \
day_night_percentage_face_loop, \
day_night_percentage_face_resign, \
NULL, \
})
#endif // DAY_NIGHT_PERCENTAGE_FACE_H_

View File

@@ -0,0 +1,176 @@
/*
* MIT License
*
* Copyright (c) 2023 Curtis J. Brown <mrbrown8@juno.com>
*
* 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 "decimal_time_face.h"
#include "watch.h"
void decimal_time_face_setup(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) watch_face_index;
(void) context_ptr;
// 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(decimal_time_face_state_t));
decimal_time_face_state_t *state = (decimal_time_face_state_t *)*context_ptr;
state->chime_enabled = false;
state->features_to_show = 0 ;
}
}
void decimal_time_face_activate(void *context) {
// same as above: silence the warning, we don't need to check the settings.
// we do however need to set some things in our context. Here we cast it to the correct type...
decimal_time_face_state_t *state = (decimal_time_face_state_t *)context;
watch_set_indicator(WATCH_INDICATOR_24H); // This face is always 24h, so just set the indicators
watch_clear_indicator(WATCH_INDICATOR_PM);
if (state->chime_enabled) {
watch_set_indicator(WATCH_INDICATOR_BELL);
}
}
bool decimal_time_face_loop(movement_event_t event, void *context) {
decimal_time_face_state_t *state = (decimal_time_face_state_t *)context;
char buf[16];
uint8_t centihours, decimal_seconds;
watch_date_time_t date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
// on activate and tick
date_time = watch_rtc_get_date_time();
centihours = (( date_time.unit.minute * 60 + date_time.unit.second ) * 100 ) / 3600; // Integer division, fractions get dropped, no need for abs() (bonus)
decimal_seconds = ( date_time.unit.minute * 60 + date_time.unit.second ) % 36 ;
switch (state->features_to_show) {
case 0:
sprintf( buf, "dT %02d%02d ", date_time.unit.hour, centihours );
break;
case 1:
sprintf( buf, "dT %02d%02d%2d", date_time.unit.hour, centihours, decimal_seconds );
break;
case 2:
sprintf( buf, "dT%2d%02d%02d ", date_time.unit.day, date_time.unit.hour, centihours );
break;
case 3:
sprintf( buf, "dT%2d%02d%02d%2d", date_time.unit.day, date_time.unit.hour, centihours, decimal_seconds );
break;
}
watch_display_string(buf, 0); // display calculated time
// at the top of every hour...
if (date_time.unit.minute == 0 && date_time.unit.second == 0 && state->chime_enabled) {
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 15);
watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
break;
case EVENT_ALARM_LONG_PRESS:
state->chime_enabled = !state->chime_enabled; // just like from simple_watch_face
if (state->chime_enabled)
watch_set_indicator(WATCH_INDICATOR_BELL);
else
watch_clear_indicator(WATCH_INDICATOR_BELL);
break;
case EVENT_ALARM_BUTTON_UP:
state->features_to_show += 1 ; // cycle thru what's to be shown
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);
break;
}
return true;
}
void decimal_time_face_resign(void *context) {
// our watch face, like most watch faces, has nothing special to do when resigning.
// there are no peripherals or sensors here to worry about turning off.
(void) context;
}
// void decimal_time_face_advise() {
//
// }

View File

@@ -0,0 +1,71 @@
/*
* MIT License
*
* Copyright (c) 2023 Curtis J. Brown <mrbrown8@juno.com>
*
* 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 DECIMAL_TIME_FACE_H_
#define DECIMAL_TIME_FACE_H_
/*
* DECIMAL TIME face
*
* This face presents the current time as hours and hundredths of an hour. Every hundreth of an hour, or "centihour",
* occurs every 36 seconds. Because they range from 0 to 99, centihours, in the seventies range, will be displayed with a lowercase 7.
*
* See https://en.wikipedia.org/wiki/Decimal_time#Decimal_hours
*
* This method of timekeeping is used by the United States Postal Service.
* http://www.branch38nalc.com/sitebuildercontent/sitebuilderfiles/CONVERSION_TABLE_TIME.pdf
* https://postalemployeenetwork.com/time-conversion-print.htm
*
* This method may be used by other organizations as well
* https://www.labor.nc.gov/workplace-rights/employer-responsibilities/time-conversion-chart-minutes-decimal-hours
* https://uh.edu/office-of-finance/payroll/time_conversion_chart_minutes_to_decimalhours.pdf
* https://www.placer.ca.gov/DocumentCenter/View/3860/Decimals-to-Minutes-Conversion-Table-PDF
* https://hr.colostate.edu/minute-to-decimal-conversion-chart/
*
* Many thanks go to Joey Castillo for making this project happen.
*/
#include "movement.h"
typedef struct {
bool chime_enabled; // did the user enable hourly chime for this face?
uint8_t features_to_show : 2 ; // what features are to be displayed?
} decimal_time_face_state_t;
void decimal_time_face_setup(uint8_t watch_face_index, void ** context_ptr);
void decimal_time_face_activate(void *context);
bool decimal_time_face_loop(movement_event_t event, void *context);
void decimal_time_face_resign(void *context);
// void decimal_time_face_advise();
#define decimal_time_face ((const watch_face_t){ \
decimal_time_face_setup, \
decimal_time_face_activate, \
decimal_time_face_loop, \
decimal_time_face_resign, \
NULL, \
})
#endif // DECIMAL_TIME_FACE_H_

View File

@@ -0,0 +1,242 @@
/*
* MIT License
*
* Copyright (c) 2023 CarpeNoctem
*
* 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 "french_revolutionary_face.h"
void french_revolutionary_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(french_revolutionary_state_t));
memset(*context_ptr, 0, sizeof(french_revolutionary_state_t));
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
french_revolutionary_state_t *state = (french_revolutionary_state_t *)*context_ptr;
state->use_am_pm = false;
state->show_seconds = true;
state->display_type = 0;
state->colon_set_after_splash = false;
}
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
}
void french_revolutionary_face_activate(void *context) {
french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
// Handle any tasks related to your watch face coming on screen.
state->colon_set_after_splash = false;
}
bool french_revolutionary_face_loop(movement_event_t event, void *context) {
french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
char buf[11];
watch_date_time_t date_time;
fr_decimal_time decimal_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Initial UI - Show a quick "splash screen"
watch_clear_display();
watch_display_string("FR dECimL", 0);
break;
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
decimal_time = get_decimal_time(&date_time);
set_display_buffer(buf, state, &decimal_time, &date_time);
// If we're in low-energy mode, don't write out the seconds. Also start the LE tick animation if it's not already going.
if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
buf[8] = ' ';
buf[9] = ' ';
if (!watch_sleep_animation_is_running()) { watch_start_sleep_animation(500); }
}
// Update the display with our decimal time
watch_display_string(buf, 0);
// Oh, and a one-off to set the colon after the "splash screen"
if (!state->colon_set_after_splash) {
watch_set_colon();
state->colon_set_after_splash = true;
}
break;
case EVENT_ALARM_BUTTON_UP:
state->display_type += 1 ; // cycle through the display types
if (state->display_type > 2) { state->display_type = 0; } // but return to 0 after 2
break;
case EVENT_ALARM_LONG_PRESS:
// I originally had chiming on the decimal-hour enabled, and this would enable/disable that chime, just like on
// the simple clock and decimal time faces. But because decimal seconds don't always line up with normal seconds,
// I assume the (decimal-)hourly chime could sometimes be missed. Additionally, I need this button for other purposes,
// now that I added seconds on/off toggle and upper normal-time with the ability to toggle that between 12/24hr format.
state->show_seconds = !state->show_seconds;
if (!state->show_seconds) { watch_display_string(" ", 8); }
else { watch_display_string("--", 8); }
break;
case EVENT_LIGHT_LONG_PRESS:
// In case anyone really wants that upper time in 12-hour format. I thought about using the global setting (movement_clock_mode_24h())
// for this preference, but thought someone who prefers 12-hour format normally, might prefer 24hr when compared to a 10hr decimal day,
// so this is separate for now.
state->use_am_pm = !state->use_am_pm;
if (state->use_am_pm) {
watch_clear_indicator(WATCH_INDICATOR_24H);
date_time = watch_rtc_get_date_time();
if (date_time.unit.hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); }
else { watch_set_indicator(WATCH_INDICATOR_PM); }
} else {
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_set_indicator(WATCH_INDICATOR_24H);
}
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);
}
// 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 french_revolutionary_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}
// Calculate decimal time from normal (24hr) time
fr_decimal_time get_decimal_time(watch_date_time_t *date_time) {
uint32_t current_24hr_secs, current_decimal_seconds;
fr_decimal_time decimal_time;
// Current 24-hr time in seconds (There are 86400 of these in a day.)
current_24hr_secs = date_time->unit.hour * 3600 + date_time->unit.minute * 60 + date_time->unit.second;
// Current Decimal Time in seconds. There are 100000 seconds in a 10-hr decimal-time day.
// current_decimal_seconds = current_24hr_seconds * 100000 / 86400, or = current_24_seconds * 1000 / 864;
// By chopping the extra zeros off the end, we can use uint32 instead of uint64.
current_decimal_seconds = current_24hr_secs * 1000 / 864;
decimal_time.hour = current_decimal_seconds / 10000;
// Remove the hours from total seconds and keep the remainder for below.
current_decimal_seconds = current_decimal_seconds - decimal_time.hour * 10000;
decimal_time.minute = current_decimal_seconds / 100;
// Remove the minutes from total seconds and keep the remaining seconds
// Note: I think I used an extra seconds variable here because sprintf or movement weren't liking a uint32...
decimal_time.second = current_decimal_seconds - decimal_time.minute * 100;
return decimal_time;
}
// Fills in the display buffer, depending on the currently-selected display option (and sub-options):
// - Decimal-time only
// - Decimal-time with date in top-right
// - Decimal-time with normal time in the top (minutes first, then hours, due to display limitations)
// TODO: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here.
// I'll try to add that optimization could be added in a future commit.
void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time_t *date_time) {
switch (state->display_type) {
// Decimal time only
case 0:
// Originally I had the day slot set to "FR" (French Revolutionary time), but my brain kept thinking "Friday" whenever I saw it,
// so I changed it to dT (Decimal Time) to avoid that confusion. Apologies to anyone who has the other decimal_time face and this one
// installed concurrently. Maybe the splash screen will help a little.
sprintf( buf, "dT %2d%02d%02d", decimal_time->hour, decimal_time->minute, decimal_time->second );
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_clear_indicator(WATCH_INDICATOR_24H);
break;
// Decimal time and date
case 1:
sprintf( buf, "dT%2d%2d%02d%02d", date_time->unit.day, decimal_time->hour, decimal_time->minute, decimal_time->second );
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_clear_indicator(WATCH_INDICATOR_24H);
break;
// Decimal time on bottom, normal time above
case 2:
if (state->use_am_pm) {
// if we are in 12 hour mode, do some cleanup.
watch_clear_indicator(WATCH_INDICATOR_24H);
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;
} else {
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_set_indicator(WATCH_INDICATOR_24H);
}
// Note, the date digits don't display a leading zero well, so we don't use it.
sprintf( buf, "%02d%2d%2d%02d%02d", date_time->unit.minute, date_time->unit.hour, decimal_time->hour, decimal_time->minute, decimal_time->second );
// Make the second character of the Day area more readable
buf[1] = fix_character_one(buf[1]);
break;
}
// Finally, if show_seconds is disabled, trim those off.
if (!state->show_seconds) {
buf[8] = ' ';
buf[9] = ' ';
}
}
// Sadly, the second character of the Day field cannot show all numbers, so we make some replacements.
// See https://www.sensorwatch.net/docs/wig/display/#limitations-of-the-weekday-digits
char fix_character_one(char digit) {
char return_char = digit; // We don't need to update this for 0, 1, 3, 7 and 8.
switch(digit) {
case '2':
// Roman numeral / tally representation of 2
return_char = '|'; // Thanks, Joey, for already having this in the character set.
break;
case '4':
// Looks almost like a 4 - just missing the top-left segment.
// 0b01000110
return_char = '&'; // Slight hack - I want 0b01000110, but 0b01000100 is already in the character set and will do, since B and C segments are linked in this position.
break;
case '5':
return_char = 'F'; // F for Five
break;
case '6':
return_char = 'E'; // Looks almost like a 6 - just missing the bottom-right segment. Not super happy with it, but liked it best of the options I tried.
break;
case '9':
return_char = 'N'; // N for Nine
break;
}
return return_char;
}

View File

@@ -0,0 +1,84 @@
/*
* MIT License
*
* Copyright (c) 2023 CarpeNoctem
*
* 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 FRENCH_REVOLUTIONARY_FACE_H_
#define FRENCH_REVOLUTIONARY_FACE_H_
#include "movement.h"
/*
* French Revolutionary Decimal Time
*
* Similar to the Decimal Time face, but with the day divided into ten hours instead of twenty four.
* Each hour is divided into one hundred minutes, and those minutes are divided into 100 seconds.
* I came across this one the Svalbard watch site here: https://svalbard.watch/pages/about_decimal_time.html
* More info here as well: https://en.wikipedia.org/wiki/Decimal_time
*
* By default, the face just displays the current decimal time. Pressing the alarm button will toggle through other display options:
* 1) Just decimal time (with dT indicator at top)
* 2) Decimal time, with dT indicator and date above.
* 3) Decimal time, with 24-hr time above (where Day and Date would normally be displayed), BUT minutes first then hours.
* Sadly, the first character of the date area only goes up to 3 (see https://www.sensorwatch.net/docs/wig/display/#the-day-digits)
* I was going to begrudgindly leave this display option out when I realized that, but thought it would be better to have this backwards
* representation of the "normal" time than not at all.
*
* A long-press of the light button will toggle the upper time between 12-hr AM/PM and 24-hr mode. I thought of reading the main setting for this,
* but thought that a person could normally prefer 12hr time, but next to a 10hr day want to see the normal time in the 24hr format.
*
* A long-press of the alarm button will toggle the seconds off and on.
*
*/
typedef struct {
bool use_am_pm; // Use 12-hr AM/PM for upper display instead of 24-hr? (Default is 24-hr)
bool show_seconds;
bool colon_set_after_splash;
uint8_t display_type : 2;
} french_revolutionary_state_t;
typedef struct {
uint8_t second : 8; // 0-99
uint8_t minute : 8; // 0-99
uint8_t hour : 5; // 0-10
} fr_decimal_time;
void french_revolutionary_face_setup(uint8_t watch_face_index, void ** context_ptr);
void french_revolutionary_face_activate(void *context);
bool french_revolutionary_face_loop(movement_event_t event, void *context);
void french_revolutionary_face_resign(void *context);
char fix_character_one(char digit);
fr_decimal_time get_decimal_time(watch_date_time_t *date_time);
void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time_t *date_time);
#define french_revolutionary_face ((const watch_face_t){ \
french_revolutionary_face_setup, \
french_revolutionary_face_activate, \
french_revolutionary_face_loop, \
french_revolutionary_face_resign, \
NULL, \
})
#endif // FRENCH_REVOLUTIONARY_FACE_H_

View File

@@ -0,0 +1,163 @@
/*
* MIT License
*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "watch_utility.h"
#include "mars_time_face.h"
// note: lander coordinates come from Mars24's `marslandmarks.xml` file
static double site_longitudes[MARS_TIME_NUM_SITES] = {
0, // Mars Coordinated Time, at the meridian
360.0 - 109.9, // Zhurong lander site
360.0 - 77.45088572, // Perseverance lander site
360.0 - 135.623447, // InSight lander site
360.0 - 137.441635, // Curiosity lander site
};
static char site_names[MARS_TIME_NUM_SITES][3] = {
"MC",
"ZH",
"PE",
"IN",
"CU",
};
static uint16_t landing_sols[MARS_TIME_NUM_SITES] = {
0,
52387,
52304,
51511,
49269,
};
typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
} mars_clock_hms_t;
static void _h_to_hms(mars_clock_hms_t *date_time, double h) {
unsigned int seconds = (unsigned int)(h * 3600.0);
date_time->hour = seconds / 3600;
seconds = seconds % 3600;
date_time->minute = floor(seconds / 60);
date_time->second = round(seconds % 60);
}
static void _update(mars_time_state_t *state) {
char buf[11];
watch_date_time_t date_time = watch_rtc_get_date_time();
uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
// TODO: I'm skipping over some steps here.
// https://www.giss.nasa.gov/tools/mars24/help/algorithm.html
double jdut = 2440587.5 + ((double)now / 86400.0);
double jdtt = jdut + ((37.0 + 32.184) / 86400.0);
double jd2k = jdtt - 2451545.0;
double msd = ((jd2k - 4.5) / 1.0274912517) + 44796.0 - 0.0009626;
double mtc = fmod(24 * msd, 24);
double lmt;
if (state->current_site == 0) {
lmt = mtc;
} else {
double longitude = site_longitudes[state->current_site];
double lmst = mtc - ((longitude * 24.0) / 360.0);
lmt = fmod(lmst + 24, 24);
}
if (state->displaying_sol) {
// TODO: this is not right, mission sol should turn over at midnight local time?
uint16_t sol = floor(msd) - landing_sols[state->current_site];
if (sol < 1000) sprintf(&buf[0], "%s Sol%3d", site_names[state->current_site], sol);
else sprintf(&buf[0], "%s $%6d", site_names[state->current_site], sol);
watch_clear_colon();
watch_clear_indicator(WATCH_INDICATOR_24H);
} else {
mars_clock_hms_t mars_time;
_h_to_hms(&mars_time, lmt);
sprintf(&buf[0], "%s %02d%02d%02d", site_names[state->current_site], mars_time.hour, mars_time.minute, mars_time.second);
watch_set_colon();
watch_set_indicator(WATCH_INDICATOR_24H);
}
watch_display_string(buf, 0);
}
void mars_time_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(mars_time_state_t));
memset(*context_ptr, 0, sizeof(mars_time_state_t));
}
}
void mars_time_face_activate(void *context) {
mars_time_state_t *state = (mars_time_state_t *)context;
(void) state;
}
bool mars_time_face_loop(movement_event_t event, void *context) {
mars_time_state_t *state = (mars_time_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
_update(state);
break;
case EVENT_LIGHT_BUTTON_UP:
state->displaying_sol = !state->displaying_sol;
_update(state);
break;
case EVENT_LIGHT_LONG_PRESS:
movement_illuminate_led();
break;
case EVENT_ALARM_BUTTON_UP:
state->current_site = (state->current_site + 1) % MARS_TIME_NUM_SITES;
_update(state);
break;
case EVENT_TIMEOUT:
// TODO: make this lower power so we can avoid timeout
movement_move_to_face(0);
break;
case EVENT_LOW_ENERGY_UPDATE:
// TODO: low energy update
// watch_start_sleep_animation(500);
break;
case EVENT_LIGHT_BUTTON_DOWN:
// don't light up every time light is hit
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void mars_time_face_resign(void *context) {
(void) context;
}

View File

@@ -0,0 +1,84 @@
/*
* MIT License
*
* 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 MARS_TIME_FACE_H_
#define MARS_TIME_FACE_H_
/*
* MARS TIME face
*
* This watch face is dedicated to Martian timekeeping.
* It has several modes, and can display either a time or a date.
*
* Pressing the ALARM button cycles through different time zones on Mars:
* MC - Mars Coordinated Time, the time at Airy-0 Crater on the Martian prime meridian
* ZH - Local mean solar time for the Zhurong rover
* PE - LMST for the Perseverance rover
* IN - LMST for the Insight lander
* CU - LMST for the Curiosity rover
*
* Press the LIGHT button to toggle between displaying time and date:
* MC S - the Mars Sol Date, Martian days since December 29, 1873
* ZH Sol - Mission sol for the Zhurong rover
* PE Sol - Mission sol for the Perseverance rover
* IN S - Mission sol for the InSight lander
* CU S - Mission sol for the Curiosity rover
*
* Note that where the mission sol is below 1000, this watch face displays
* the word “Sol” on the bottom line. When the mission sol is over 1000, the
* word “Sol” will not fit and so it displays a stylized letter S at the top
* right.
*/
#include "movement.h"
typedef enum {
MARS_TIME_MERIDIAN,
MARS_TIME_ZHURONG_SITE,
MARS_TIME_PERSEVERANCE_SITE,
MARS_TIME_INSIGHT_SITE,
MARS_TIME_CURIOSITY_SITE,
MARS_TIME_NUM_SITES,
} mars_time_site_t;
typedef struct {
mars_time_site_t current_site;
bool displaying_sol;
} mars_time_state_t;
void mars_time_face_setup(uint8_t watch_face_index, void ** context_ptr);
void mars_time_face_activate(void *context);
bool mars_time_face_loop(movement_event_t event, void *context);
void mars_time_face_resign(void *context);
#define mars_time_face ((const watch_face_t){ \
mars_time_face_setup, \
mars_time_face_activate, \
mars_time_face_loop, \
mars_time_face_resign, \
NULL, \
})
#endif // MARS_TIME_FACE_H_

View File

@@ -0,0 +1,114 @@
/*
* MIT License
*
* Copyright (c) 2023 Dennisman219
*
* 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 "minimal_clock_face.h"
static void _minimal_clock_face_update_display() {
watch_date_time_t date_time = watch_rtc_get_date_time();
char buffer[11];
if (!movement_clock_mode_24h()) {
date_time.unit.hour %= 12;
sprintf(buffer, "%2d%02d ", date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buffer, "%02d%02d ", date_time.unit.hour, date_time.unit.minute);
}
watch_display_string(buffer, 4);
}
void minimal_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(minimal_clock_state_t));
memset(*context_ptr, 0, sizeof(minimal_clock_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 minimal_clock_face_activate(void *context) {
(void) context;
// Handle any tasks related to your watch face coming on screen.
watch_set_colon();
}
bool minimal_clock_face_loop(movement_event_t event, void *context) {
(void) context;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Show your initial UI here.
_minimal_clock_face_update_display();
break;
case EVENT_TICK:
// If needed, update your display here.
_minimal_clock_face_update_display();
break;
case EVENT_LIGHT_BUTTON_UP:
// You can use the Light button for your own purposes. Note that by default, Movement will also
// illuminate the LED in response to EVENT_LIGHT_BUTTON_DOWN; to suppress that behavior, add an
// empty case for EVENT_LIGHT_BUTTON_DOWN.
break;
case EVENT_ALARM_BUTTON_UP:
// Just in case you have need for another button.
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_sleep_animation(500);
_minimal_clock_face_update_display();
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);
}
// 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 minimal_clock_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,57 @@
/*
* MIT License
*
* Copyright (c) 2023 Dennisman219
*
* 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 MINIMAL_CLOCK_FACE_H_
#define MINIMAL_CLOCK_FACE_H_
#include "movement.h"
/*
* MINIMAL CLOCK FACE
*
* A minimal clock face that just shows hours and minutes.
* There is nothing to configure. The face follows the 12h/24h setting
*
*/
typedef struct {
// Anything you need to keep track of, put it here!
uint8_t unused;
} minimal_clock_state_t;
void minimal_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void minimal_clock_face_activate(void *context);
bool minimal_clock_face_loop(movement_event_t event, void *context);
void minimal_clock_face_resign(void *context);
#define minimal_clock_face ((const watch_face_t){ \
minimal_clock_face_setup, \
minimal_clock_face_activate, \
minimal_clock_face_loop, \
minimal_clock_face_resign, \
NULL, \
})
#endif // MINIMAL_CLOCK_FACE_H_

View File

@@ -0,0 +1,238 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau - original repetition_minute_face
* Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_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.
*/
/*
* This face, minute_repeater_decimal_face, is a modification of the original
* repetition_minute_face by Jonas Termeau.
*
* This version was created by BrianBinFL to use a decimal minute repeater pattern
* (hours, tens, and minutes) instead of the traditional pattern (hours, quarters,
* minutes).
*
* Also 500ms delays were added after the hours segment and after the tens segment
* to make it easier for the user to realize that the counting for the current
* segment has ended.
*
*/
#include <stdlib.h>
#include "minute_repeater_decimal_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
void mrd_play_hour_chime(void) {
watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
void mrd_play_tens_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 mrd_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, minute_repeater_decimal_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 minute_repeater_decimal_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(minute_repeater_decimal_state_t));
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)*context_ptr;
state->signal_enabled = false;
state->watch_face_index = watch_face_index;
}
}
void minute_repeater_decimal_face_activate(void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_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(movement_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 minute_repeater_decimal_face_loop(movement_event_t event, void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t 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 (!movement_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_sleep_animation_is_running()) watch_start_sleep_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 != movement_alarm_enabled()) _update_alarm_indicator(movement_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:
movement_play_signal();
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 tens = date_time.unit.minute / 10;
int minutes = date_time.unit.minute % 10;
// chiming hours
if (!movement_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) {
mrd_play_hour_chime();
}
// do a little pause before proceeding to tens
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
// chiming tens (if needed)
if (tens > 0) {
int count = 0;
for(count = tens; count > 0; --count) {
mrd_play_tens_chime();
}
// do a little pause before proceeding to minutes
watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
}
// chiming minutes (if needed)
if (minutes > 0) {
int count = 0;
for(count = minutes; count > 0; --count) {
mrd_play_minute_chime();
}
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void minute_repeater_decimal_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t minute_repeater_decimal_face_advise(void *context) {
minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -0,0 +1,84 @@
/*
* MIT License
*
* Copyright (c) 2023 Jonas Termeau - original repetition_minute_face
* Copyright (c) 2023 Brian Blakley - modified minute_repeater_decimal_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 MINUTE_REPEATER_DECIMAL_FACE_H_
#define MINUTE_REPEATER_DECIMAL_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, decimal 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..9 low-high couple pitched beeps for the tens of minutes
* 0..9 high pitched beep(s) for the remaining minutes (ones of 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;
} minute_repeater_decimal_state_t;
void mrd_play_hour_chime(void);
void mrd_play_tens_chime(void);
void mrd_play_minute_chime(void);
void minute_repeater_decimal_face_setup(uint8_t watch_face_index, void ** context_ptr);
void minute_repeater_decimal_face_activate(void *context);
bool minute_repeater_decimal_face_loop(movement_event_t event, void *context);
void minute_repeater_decimal_face_resign(void *context);
movement_watch_face_advisory_t minute_repeater_decimal_face_advise(void *context);
#define minute_repeater_decimal_face ((const watch_face_t){ \
minute_repeater_decimal_face_setup, \
minute_repeater_decimal_face_activate, \
minute_repeater_decimal_face_loop, \
minute_repeater_decimal_face_resign, \
minute_repeater_decimal_face_advise, \
})
#endif // MINUTE_REPEATER_DECIMAL_FACE_H_

View File

@@ -0,0 +1,221 @@
/*
* 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(uint8_t watch_face_index, void ** context_ptr) {
(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(void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_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(movement_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, void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t 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 (!movement_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_sleep_animation_is_running()) watch_start_sleep_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 != movement_alarm_enabled()) _update_alarm_indicator(movement_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);
movement_play_signal();
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 (!movement_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);
}
return true;
}
void repetition_minute_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t repetition_minute_face_advise(void *context) {
repetition_minute_state_t *state = (repetition_minute_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -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_
/*
* REPETITION MINUTE face
*
* 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 ~
*/
#include "movement.h"
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(uint8_t watch_face_index, void ** context_ptr);
void repetition_minute_face_activate(void *context);
bool repetition_minute_face_loop(movement_event_t event, void *context);
void repetition_minute_face_resign(void *context);
movement_watch_face_advisory_t repetition_minute_face_advise(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_advise, \
})
#endif // REPETITION_MINUTE_FACE_H_

View File

@@ -0,0 +1,222 @@
/*
* MIT License
*
* Copyright (c) 2023 Andreas Nebinger, based on Joey Castillo's simple clock 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.
*/
#include <stdlib.h>
#include <string.h>
#include "simple_clock_bin_led_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
static void _update_alarm_indicator(bool settings_alarm_enabled, simple_clock_bin_led_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);
}
static void _display_left_aligned(uint8_t value) {
if (value >= 10) {
watch_display_character('0' + value / 10, 4);
watch_display_character('0' + value % 10, 5);
} else {
watch_display_character('0' + value, 4);
watch_display_character(' ', 5);
}
}
void simple_clock_bin_led_face_setup(uint8_t watch_face_index, void ** context_ptr) {
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(simple_clock_bin_led_state_t));
memset(*context_ptr, 0, sizeof(simple_clock_bin_led_state_t));
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)*context_ptr;
state->watch_face_index = watch_face_index;
}
}
void simple_clock_bin_led_face_activate(void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_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(movement_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 simple_clock_bin_led_face_loop(movement_event_t event, void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t 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();
if (state->flashing_state > 0) {
if (state->ticks) {
state->ticks--;
} else {
if (state->flashing_state & 64) {
// start led on for current bit
state->flashing_state &= 63;
state->ticks = (state->flashing_value & 1 ? 7 : 1);
movement_illuminate_led();
} else {
// indicate first or switch to next bit
watch_set_led_off();
if ((state->flashing_state & 128) == 0) state->flashing_value = state->flashing_value >> 1;
if (state->flashing_value || (state->flashing_state & 128)) {
state->flashing_state &= 127;
state->flashing_state |= 64;
state->ticks = 6;
} else if (state->flashing_state & 1) {
// transition to minutes
state->flashing_state = 2 + 128;
state->flashing_value = date_time.unit.minute;
_display_left_aligned(state->flashing_value);
state->ticks = 9;
} else {
// end flashing
state->flashing_state = 0;
state->previous_date_time = 0xFFFFFFFF;
movement_request_tick_frequency(1);
watch_set_colon();
}
}
}
} else {
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 (!movement_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_sleep_animation_is_running()) watch_start_sleep_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 != movement_alarm_enabled()) _update_alarm_indicator(movement_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);
movement_play_signal();
break;
case EVENT_LIGHT_LONG_PRESS:
if (state->flashing_state == 0) {
date_time = watch_rtc_get_date_time();
state->flashing_state = 1 + 128;
state->ticks = 4;
if (!movement_clock_mode_24h()) {
date_time.unit.hour %= 12;
if (date_time.unit.hour == 0) date_time.unit.hour = 12;
}
watch_display_string(" ", 4);
_display_left_aligned(date_time.unit.hour);
state->flashing_value = date_time.unit.hour > 12 ? date_time.unit.hour - 12 : date_time.unit.hour;
watch_set_led_off();
watch_clear_colon();
movement_request_tick_frequency(8);
}
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void simple_clock_bin_led_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t simple_clock_bin_led_face_advise(void *context) {
simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -0,0 +1,82 @@
/*
* MIT License
*
* Copyright (c) 2023 Andreas Nebinger, based on Joey Castillo's simple clock 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 SIIMPLE_CLOCK_BIN_LED_FACE_H_
#define SIIMPLE_CLOCK_BIN_LED_FACE_H_
/*
* BINARY LED CLOCK FACE
*
* A "fork" of the simple clock face, which provides the functionality of showing
* the current time by flashing the LED using binary representation.
*
* This feature serves as a practical solution to compensate for the admittedly
* subpar backlight of the F91w watch and is especially useful if your eyesight
* is not the best. By pressing and holding the light button long enough, the
* watch will illuminate the LED to showcase the current time.
*
* How to interpret the flashing led:
* - Firstly, the hour is presented as a binary number, with the lowest bit displayed
* first. A short flash signifies 0, while a longer flash represents 1. If you use
* the watch in 24h mode, please note that the indicated value may be decreased
* by 12, to keep things simple and short. For example, 22h would be translated
* to 10h.
* - After showing the hour, a lengthier pause indicates that minutes will be shown
* next.
* - Similar to the hour representation, minutes are displayed in binary format,
* starting with the lowest bits. A short flash denotes 0, a longer flash
* represents 1.
*/
#include "movement.h"
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;
uint8_t flashing_state; // bitmap representing the flashing state. Bit 0 = hours showing, bit 1 = minutes showing,
// bit 6 = short break between flashing bits, bit 7 = long break between hours and minutes
uint8_t flashing_value;
uint8_t ticks;
} simple_clock_bin_led_state_t;
void simple_clock_bin_led_face_setup(uint8_t watch_face_index, void ** context_ptr);
void simple_clock_bin_led_face_activate(void *context);
bool simple_clock_bin_led_face_loop(movement_event_t event, void *context);
void simple_clock_bin_led_face_resign(void *context);
movement_watch_face_advisory_t simple_clock_bin_led_face_advise(void *context);
#define simple_clock_bin_led_face ((const watch_face_t){ \
simple_clock_bin_led_face_setup, \
simple_clock_bin_led_face_activate, \
simple_clock_bin_led_face_loop, \
simple_clock_bin_led_face_resign, \
simple_clock_bin_led_face_advise, \
})
#endif // SIIMPLE_CLOCK_BIN_LED_FACE_H_

View File

@@ -0,0 +1,156 @@
/*
* MIT License
*
* 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.
*/
#include <stdlib.h>
#include "weeknumber_clock_face.h"
#include "watch.h"
#include "watch_utility.h"
static void _update_alarm_indicator(bool settings_alarm_enabled, weeknumber_clock_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 weeknumber_clock_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(weeknumber_clock_state_t));
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)*context_ptr;
state->signal_enabled = false;
state->watch_face_index = watch_face_index;
}
}
void weeknumber_clock_face_activate(void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
if (watch_sleep_animation_is_running()) watch_stop_sleep_animation();
if (movement_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(movement_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 weeknumber_clock_face_loop(movement_event_t event, void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
char buf[11];
uint8_t pos;
watch_date_time_t 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 >> 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, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day));
} else {
// other stuff changed; let's do it all.
if (!movement_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_sleep_animation_is_running()) watch_start_sleep_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, watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day));
}
}
watch_display_string(buf, pos);
// handle alarm indicator
if (state->alarm_enabled != movement_alarm_enabled()) _update_alarm_indicator(movement_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);
movement_play_signal();
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void weeknumber_clock_face_resign(void *context) {
(void) context;
}
movement_watch_face_advisory_t weeknumber_clock_face_advise(void *context) {
weeknumber_clock_state_t *state = (weeknumber_clock_state_t *)context;
movement_watch_face_advisory_t retval = { 0 };
if (state->signal_enabled) {
watch_date_time_t date_time = watch_rtc_get_date_time();
retval.wants_background_task = date_time.unit.minute == 0;
}
return retval;
}

View File

@@ -0,0 +1,61 @@
/*
* MIT License
*
* 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 WEEKNUMBER_CLOCK_FACE_H_
#define WEEKNUMBER_CLOCK_FACE_H_
/*
* WEEK-NUMBER WATCH FACE
*
* Same as simple clock, but has iso 8601 week number instead of seconds counter.
*
* Long-press ALARM to toggle the hourly chime.
*/
#include "movement.h"
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;
} weeknumber_clock_state_t;
void weeknumber_clock_face_setup(uint8_t watch_face_index, void ** context_ptr);
void weeknumber_clock_face_activate(void *context);
bool weeknumber_clock_face_loop(movement_event_t event, void *context);
void weeknumber_clock_face_resign(void *context);
movement_watch_face_advisory_t weeknumber_clock_face_advise(void *context);
#define weeknumber_clock_face ((const watch_face_t){ \
weeknumber_clock_face_setup, \
weeknumber_clock_face_activate, \
weeknumber_clock_face_loop, \
weeknumber_clock_face_resign, \
weeknumber_clock_face_advise, \
})
#endif // SIMPLE_CLOCK_FACE_H_

View File

@@ -0,0 +1,374 @@
/*
* 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.
*/
#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(uint8_t watch_face_index, void **context_ptr)
{
(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(void *context)
{
world_clock2_state_t *state = (world_clock2_state_t *) context;
if (watch_sleep_animation_is_running())
watch_stop_sleep_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, world_clock2_state_t *state)
{
char buf[11];
uint8_t pos;
uint32_t timestamp;
uint32_t previous_date_time;
watch_date_time_t 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 (movement_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_get_current_timezone_offset());
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 (!movement_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_sleep_animation_is_running())
watch_start_sleep_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 (movement_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);
}
return true;
}
static bool mode_settings(movement_event_t event, 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 (movement_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 (movement_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);
}
return true;
}
bool world_clock2_face_loop(movement_event_t event, 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, state);
case WORLD_CLOCK2_MODE_SETTINGS:
return mode_settings(event, state);
}
return false;
}
void world_clock2_face_resign(void *context)
{
(void) context;
}

View File

@@ -0,0 +1,122 @@
/*
* 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_
/*
* 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.
*/
/* 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(uint8_t watch_face_index, void **context_ptr);
void world_clock2_face_activate(void *context);
bool world_clock2_face_loop(movement_event_t event, void *context);
void world_clock2_face_resign(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_ */

View File

@@ -0,0 +1,207 @@
/*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include "wyoscan_face.h"
#include "watch_private_display.h"
/*
Slowly render the current time from left to right,
scanning across its liquid crystal face, completing 1 cycle every 2 seconds.
Created to mimic the wyoscan watch that was produced by Halmos and designed by Dexter Sinister
It looks like this https://www.o-r-g.com/apps/wyoscan
Youll notice that reading this watch requires more attention than usual,
as the seven segments of each digit are lit one by one across its display.
This speed may be adjusted until it reaches the limits of your perception.
You and your watch are now in tune.
This is a relatively generic way of animating a time display.
If you want to modify the animation, you can change the segment_map
the A-F are corresponding to the segments on the watch face
A
F B
G
E C
D
the X's are the frames that will be skipped in the animation
This particular segment_map allocates 8 frames to display each number
this is to achieve the 2 second cycle time.
8 frames per number * 6 numbers + the trailing 16 frames = 64 frames
at 32 frames per second, this is a 2 second cycle time.
I tried to make the animation of each number display similar to if you were
to draw the number on the watch face with a pen, pausing with 'X'
when your pen might turn a corner or when you might cross over
a line you've already drawn. It is vaguely top to bottom and counter,
clockwise when possible.
*/
static char *segment_map[] = {
"AXFBDEXC", // 0
"BXXXCXXX", // 1
"ABGEXXXD", // 2
"ABGXXXCD", // 3
"FXGBXXXC", // 4
"AXFXGXCD", // 5
"AXFEDCXG", // 6
"AXXBXXCX", // 7
"AFGCDEXB", // 8
"AFGBXXCD" // 9
};
/*
This is the mapping of input to the watch_set_pixel() function
for each position in hhmmss it defines the 2 dimention input at each of A-F*/
static const int32_t clock_mapping[6][7][2] = {
// hour 1
{{1,18}, {2,19}, {0,19}, {1,18}, {0,18}, {2,18}, {1,19}},
// hour 2
{{2,20}, {2,21}, {1,21}, {0,21}, {0,20}, {1,17}, {1,20}},
// minute 1
{{0,22}, {2,23}, {0,23}, {0,22}, {1,22}, {2,22}, {1,23}},
// minute 2
{{2,1}, {2,10}, {0,1}, {0,0}, {1,0}, {2,0}, {1,1}},
// second 1
{{2,2}, {2,3}, {0,4}, {0,3}, {0,2}, {1,2}, {1,3}},
// second 2
{{2,4}, {2,5}, {1,6}, {0,6}, {0,5}, {1,4}, {1,5}},
};
void wyoscan_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(wyoscan_state_t));
memset(*context_ptr, 0, sizeof(wyoscan_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 wyoscan_face_activate(void *context) {
wyoscan_state_t *state = (wyoscan_state_t *)context;
movement_request_tick_frequency(32);
state->total_frames = 64;
}
bool wyoscan_face_loop(movement_event_t event, void *context) {
wyoscan_state_t *state = (wyoscan_state_t *)context;
watch_date_time_t date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
break;
case EVENT_TICK:
if (!state->animate) {
date_time = watch_rtc_get_date_time();
state->start = 0;
state->end = 0;
state->animation = 0;
state->animate = true;
state->time_digits[0] = date_time.unit.hour / 10;
state->time_digits[1] = date_time.unit.hour % 10;
state->time_digits[2] = date_time.unit.minute / 10;
state->time_digits[3] = date_time.unit.minute % 10;
state->time_digits[4] = date_time.unit.second / 10;
state->time_digits[5] = date_time.unit.second % 10;
}
if ( state->animate ) {
// if we have reached the max number of illuminated segments, we clear the oldest one
if ((state->end + 1) % MAX_ILLUMINATED_SEGMENTS == state->start) {
// clear the oldest pixel if it's not 'X'
if (state->illuminated_segments[state->start][0] != 99 && state->illuminated_segments[state->start][1] != 99) {
watch_clear_pixel(state->illuminated_segments[state->start][0], state->illuminated_segments[state->start][1]);
}
// increment the start index to point to the next oldest pixel
state->start = (state->start + 1) % MAX_ILLUMINATED_SEGMENTS;
}
if (state->animation < state->total_frames - MAX_ILLUMINATED_SEGMENTS) {
if (state->animation % 32 == 0) {
if (state->colon) {
watch_set_colon();
} else {
watch_clear_colon();
}
state->colon = !state->colon;
}
// calculate the start position for the current frame
state->position = (state->animation / 8) % 6;
// calculate the current segment for the current digit
state->segment = state->animation % strlen(segment_map[state->time_digits[state->position]]);
// get the segments for the current digit
state->segments = segment_map[state->time_digits[state->position]];
if (state->segments[state->segment] == 'X') {
// if 'X', skip this frame
state->illuminated_segments[state->end][0] = 99;
state->illuminated_segments[state->end][1] = 99;
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
state->animation = (state->animation + 1);
break;
}
// calculate the animation frame
state->x = clock_mapping[state->position][state->segments[state->segment]-'A'][0];
state->y = clock_mapping[state->position][state->segments[state->segment]-'A'][1];
// set the new pixel
watch_set_pixel(state->x, state->y);
// store this pixel in the buffer
state->illuminated_segments[state->end][0] = state->x;
state->illuminated_segments[state->end][1] = state->y;
// increment the end index to the next position
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
}
else if (state->animation >= state->total_frames - MAX_ILLUMINATED_SEGMENTS && state->animation < state->total_frames) {
state->end = (state->end + 1) % MAX_ILLUMINATED_SEGMENTS;
}
else {
// reset the animation state
state->animate = false;
}
state->animation = (state->animation + 1);
}
break;
case EVENT_LOW_ENERGY_UPDATE:
break;
case EVENT_ALARM_LONG_PRESS:
break;
case EVENT_BACKGROUND_TASK:
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void wyoscan_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,91 @@
/*
* 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 WYOSCAN_FACE_H_
#define WYOSCAN_FACE_H_
/*
* WYOSCAN .5 hz watchface
*
* This is a recreation of the Wyoscan watch, which was a $175 watch in 2014.
* It was an f-91w pcb replacement.
*
* Video: https://user-images.githubusercontent.com/1795778/252550124-e07f0ed1-e328-4337-a654-fa1ee65d883f.mp4
* Background information: https://artmetropole.com/shop/11460
* Demo of what it looks like: https://www.o-r-g.com/apps/wyoscan
*
* 8 frames per number * 6 numbers + the trailing 16 frames = 64 frames
* at 32 frames per second, this is a 2-second cycle time or 0.5 Hz.
*
* It is giving me a stack overflow after about 2.5 cycles of the time display
* in the emulator, but it works fine on the watch.
*
* I'd like to make something for the low energy mode, but I haven't thought
* about how that might work, right now it just freezes in low energy mode
* until you press the 12-24HR button.
*
* There are no controls; it simply animates as long as the page is active.
*
*/
#include "movement.h"
#define MAX_ILLUMINATED_SEGMENTS 16
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;
uint8_t animation;
bool animate;
uint32_t start;
uint32_t end;
uint32_t total_frames;
bool colon;
uint8_t position, segment;
char *segments;
uint8_t x, y;
uint32_t time_digits[6];
uint32_t illuminated_segments[MAX_ILLUMINATED_SEGMENTS][2];
} wyoscan_state_t;
void wyoscan_face_setup(uint8_t watch_face_index, void ** context_ptr);
void wyoscan_face_activate(void *context);
bool wyoscan_face_loop(movement_event_t event, void *context);
void wyoscan_face_resign(void *context);
movement_watch_face_advisory_t wyoscan_face_advise(void *context);
#define wyoscan_face ((const watch_face_t){ \
wyoscan_face_setup, \
wyoscan_face_activate, \
wyoscan_face_loop, \
wyoscan_face_resign, \
NULL, \
})
#endif // WYOSCAN_FACE_H_

View File

@@ -0,0 +1,722 @@
/*
* 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_t 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_t 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(activity_state_t *state);
static uint8_t _activity_get_next_byte(uint8_t *next_byte);
void activity_face_setup(uint8_t watch_face_index, void **context_ptr) {
(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(void *context) {
(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(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_t 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(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(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_t 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_t now = watch_rtc_get_date_time();
uint8_t hour = now.unit.hour;
if (!movement_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_and_duty_cycle(period, 25);
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_and_duty_cycle(NotePeriods[BUZZER_NOTE_A5], 25);
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_t 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(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(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(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(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(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(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(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, void *context) {
activity_state_t *state = (activity_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
_activity_activate(state);
break;
case EVENT_TICK:
_activity_handle_tick(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(state);
else
state->le_state = 0;
break;
case EVENT_ALARM_LONG_PRESS:
_activity_alarm_long(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(state);
watch_start_sleep_animation(500);
}
break;
default:
movement_default_loop_handler(event);
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(void *context) {
(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);
}

View File

@@ -0,0 +1,88 @@
/*
* 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_
/*
* 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.
*/
#include "movement.h"
void activity_face_setup(uint8_t watch_face_index, void ** context_ptr);
void activity_face_activate(void *context);
bool activity_face_loop(movement_event_t event, void *context);
void activity_face_resign(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_

View File

@@ -0,0 +1,270 @@
/*
* MIT License
*
* 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "astronomy_face.h"
#include "watch_utility.h"
#if __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define NUM_AVAILABLE_BODIES 9
static const char astronomy_available_celestial_bodies[NUM_AVAILABLE_BODIES] = {
ASTRO_BODY_SUN,
ASTRO_BODY_MERCURY,
ASTRO_BODY_VENUS,
ASTRO_BODY_MOON,
ASTRO_BODY_MARS,
ASTRO_BODY_JUPITER,
ASTRO_BODY_SATURN,
ASTRO_BODY_URANUS,
ASTRO_BODY_NEPTUNE
};
static const char astronomy_celestial_body_names[NUM_AVAILABLE_BODIES][3] = {
"SO", // Sol
"ME", // Mercury
"VE", // Venus
"LU", // Moon (Luna)
"MA", // Mars
"JU", // Jupiter
"SA", // Saturn
"UR", // Uranus
"NE" // Neptune
};
static void _astronomy_face_recalculate(astronomy_state_t *state) {
#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);
double lat = (double)browser_lat / 100.0;
double lon = (double)browser_lon / 100.0;
state->latitude_radians = astro_degrees_to_radians(lat);
state->longitude_radians = astro_degrees_to_radians(lon);
}
#endif
watch_date_time_t date_time = watch_rtc_get_date_time();
uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
date_time = watch_utility_date_time_from_unix_time(timestamp, 0);
double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
astro_equatorial_coordinates_t radec_precession = astro_get_ra_dec(jd, astronomy_available_celestial_bodies[state->active_body_index], state->latitude_radians, state->longitude_radians, true);
printf("\nParams to convert: %f %f %f %f %f\n",
jd,
astro_radians_to_degrees(state->latitude_radians),
astro_radians_to_degrees(state->longitude_radians),
astro_radians_to_degrees(radec_precession.right_ascension),
astro_radians_to_degrees(radec_precession.declination));
astro_horizontal_coordinates_t horiz = astro_ra_dec_to_alt_az(jd, state->latitude_radians, state->longitude_radians, radec_precession.right_ascension, radec_precession.declination);
astro_equatorial_coordinates_t radec = astro_get_ra_dec(jd, astronomy_available_celestial_bodies[state->active_body_index], state->latitude_radians, state->longitude_radians, false);
state->altitude = astro_radians_to_degrees(horiz.altitude);
state->azimuth = astro_radians_to_degrees(horiz.azimuth);
state->right_ascension = astro_radians_to_hms(radec.right_ascension);
state->declination = astro_radians_to_dms(radec.declination);
state->distance = radec.distance;
printf("Calculated coordinates for %s on %f: \n\tRA = %f / %2dh %2dm %2ds\n\tDec = %f / %3d° %3d' %3d\"\n\tAzi = %f\n\tAlt = %f\n\tDst = %f AU\n",
astronomy_celestial_body_names[state->active_body_index],
jd,
astro_radians_to_degrees(radec.right_ascension),
state->right_ascension.hours,
state->right_ascension.minutes,
state->right_ascension.seconds,
astro_radians_to_degrees(radec.declination),
state->declination.degrees,
state->declination.minutes,
state->declination.seconds,
state->altitude,
state->azimuth,
state->distance);
}
static void _astronomy_face_update(movement_event_t event, astronomy_state_t *state) {
char buf[16];
switch (state->mode) {
case ASTRONOMY_MODE_SELECTING_BODY:
watch_clear_colon();
watch_display_string(" Astro", 4);
if (event.subsecond % 2) {
watch_display_string((char *)astronomy_celestial_body_names[state->active_body_index], 0);
} else {
watch_display_string(" ", 0);
}
if (event.subsecond == 0) {
watch_display_string(" ", 2);
switch (state->animation_state) {
case 0:
watch_set_pixel(0, 7);
watch_set_pixel(2, 6);
break;
case 1:
watch_set_pixel(1, 7);
watch_set_pixel(2, 9);
break;
case 2:
watch_set_pixel(2, 7);
watch_set_pixel(0, 9);
break;
}
state->animation_state = (state->animation_state + 1) % 3;
}
break;
case ASTRONOMY_MODE_CALCULATING:
watch_clear_display();
// this takes a moment and locks the UI, flash C for "Calculating"
watch_start_character_blink('C', 100);
_astronomy_face_recalculate(state);
watch_stop_blink();
state->mode = ASTRONOMY_MODE_DISPLAYING_ALT;
// fall through
case ASTRONOMY_MODE_DISPLAYING_ALT:
sprintf(buf, "%saL%6d", astronomy_celestial_body_names[state->active_body_index], (int16_t)round(state->altitude * 100));
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_AZI:
sprintf(buf, "%saZ%6d", astronomy_celestial_body_names[state->active_body_index], (int16_t)round(state->azimuth * 100));
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_RA:
watch_set_colon();
sprintf(buf, "ra H%02d%02d%02d", state->right_ascension.hours, state->right_ascension.minutes, state->right_ascension.seconds);
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_DEC:
watch_clear_colon();
sprintf(buf, "de %3d%2d%2d", state->declination.degrees, state->declination.minutes, state->declination.seconds);
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_DISPLAYING_DIST:
if (state->distance >= 0.00668456) {
// if >= 1,000,000 kilometers (all planets), we display distance in AU.
sprintf(buf, "diAU%6d", (uint16_t)round(state->distance * 100));
} else {
// otherwise distance in kilometers fits in 6 digits. This mode will only happen for Luna.
sprintf(buf, "di K%6ld", (uint32_t)round(state->distance * 149597871.0));
}
watch_display_string(buf, 0);
break;
case ASTRONOMY_MODE_NUM_MODES:
// this case does not happen, but we need it to silence a warning.
break;
}
}
void astronomy_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(astronomy_state_t));
memset(*context_ptr, 0, sizeof(astronomy_state_t));
}
}
void astronomy_face_activate(void *context) {
astronomy_state_t *state = (astronomy_state_t *)context;
movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
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;
state->latitude_radians = astro_degrees_to_radians(lat);
state->longitude_radians = astro_degrees_to_radians(lon);
movement_request_tick_frequency(4);
}
bool astronomy_face_loop(movement_event_t event, void *context) {
astronomy_state_t *state = (astronomy_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK:
_astronomy_face_update(event, state);
break;
case EVENT_ALARM_BUTTON_UP:
switch (state->mode) {
case ASTRONOMY_MODE_SELECTING_BODY:
// advance to next celestial body (move to calculations with a long press)
state->active_body_index = (state->active_body_index + 1) % NUM_AVAILABLE_BODIES;
break;
case ASTRONOMY_MODE_CALCULATING:
// ignore button press during calculations
break;
case ASTRONOMY_MODE_DISPLAYING_DIST:
// at last mode, wrap around
state->mode = ASTRONOMY_MODE_DISPLAYING_ALT;
break;
default:
// otherwise, advance to next mode
state->mode++;
break;
}
_astronomy_face_update(event, state);
break;
case EVENT_ALARM_LONG_PRESS:
if (state->mode == ASTRONOMY_MODE_SELECTING_BODY) {
// celestial body selected! this triggers a calculation in the update method.
state->mode = ASTRONOMY_MODE_CALCULATING;
movement_request_tick_frequency(1);
_astronomy_face_update(event, state);
} else if (state->mode != ASTRONOMY_MODE_CALCULATING) {
// in all modes except "doing a calculation", return to the selection screen.
state->mode = ASTRONOMY_MODE_SELECTING_BODY;
movement_request_tick_frequency(4);
_astronomy_face_update(event, state);
}
break;
case EVENT_TIMEOUT:
movement_move_to_face(0);
break;
case EVENT_LOW_ENERGY_UPDATE:
// TODO?
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void astronomy_face_resign(void *context) {
astronomy_state_t *state = (astronomy_state_t *)context;
state->mode = ASTRONOMY_MODE_SELECTING_BODY;
}

View File

@@ -0,0 +1,109 @@
/*
* MIT License
*
* 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 ASTRONOMY_FACE_H_
#define ASTRONOMY_FACE_H_
/*
* ASTRONOMY face
*
* The Astronomy watch face is among the most complex watch faces in the
* Movement collection. It allows you to calculate the locations of celestial
* bodies in the sky, as well as distance in astronomical units (or, in the
* case of the Moon, distance in kilometers).
*
* When you arrive at the Astronomy watch face, youll see its name (“Astro”)
* and an animation of two objects orbiting each other. You will also see “SO”
* (for Sol) flashing in the top left. The flashing letters indicate the
* currently selected celestial body. Short press Alarm to advance through
* the available celestial bodies:
*
* SO - Sol, the sun
* ME - Mercury
* VE - Venus
* LU - Luna, the Earths moon
* MA - Mars
* JU - Jupiter
* SA - Saturn
* UR - Uranus
* NE - Neptune
*
* Once youve selected the celestial body whose parameters you wish to
* calculate, long press the Alarm button and release it. The letter “C” will
* flash while the calculation is performed.
*
* When the calculation is complete, the screen will display the altitude
* (“aL”) of the celestial body. You can cycle through the available parameters
* with repeated short presses on the Alarm button:
*
* aL - Altitude (in degrees), the elevation over the horizon. If negative, it is below the horizon.
* aZ - Azimuth (in degrees), the cardinal direction relative to true north.
* rA - Right Ascension (in hours/minutes/seconds)
* dE - Declination (in degrees/minutes/seconds)
* di - Distance (the digits in the top right will display either aU for astronomical units, or K for kilometers)
*
* Long press on the Alarm button to select another celestial body.
*/
#include "movement.h"
#include "astrolib.h"
typedef enum {
ASTRONOMY_MODE_SELECTING_BODY = 0,
ASTRONOMY_MODE_CALCULATING,
ASTRONOMY_MODE_DISPLAYING_ALT,
ASTRONOMY_MODE_DISPLAYING_AZI,
ASTRONOMY_MODE_DISPLAYING_RA,
ASTRONOMY_MODE_DISPLAYING_DEC,
ASTRONOMY_MODE_DISPLAYING_DIST,
ASTRONOMY_MODE_NUM_MODES
} astronomy_mode_t;
typedef struct {
astronomy_mode_t mode;
uint8_t active_body_index;
uint8_t animation_state;
double latitude_radians; // this is the user location
double longitude_radians; // but in radians
astro_angle_hms_t right_ascension;
astro_angle_dms_t declination;
double altitude; // in decimal degrees
double azimuth; // in decimal degrees
double distance; // in AU
} astronomy_state_t;
void astronomy_face_setup(uint8_t watch_face_index, void ** context_ptr);
void astronomy_face_activate(void *context);
bool astronomy_face_loop(movement_event_t event, void *context);
void astronomy_face_resign(void *context);
#define astronomy_face ((const watch_face_t){ \
astronomy_face_setup, \
astronomy_face_activate, \
astronomy_face_loop, \
astronomy_face_resign, \
NULL, \
})
#endif // ASTRONOMY_FACE_H_

View File

@@ -0,0 +1,102 @@
/*
* MIT License
*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include "blinky_face.h"
#include "watch.h"
void blinky_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(blinky_face_state_t));
memset(*context_ptr, 0, sizeof(blinky_face_state_t));
}
}
void blinky_face_activate(void *context) {
blinky_face_state_t *state = (blinky_face_state_t *)context;
state->active = false;
}
static void _blinky_face_update_lcd(blinky_face_state_t *state) {
char buf[11];
const char colors[][7] = {" red ", " Green", " Yello"};
sprintf(buf, "BL %c%s", state->fast ? 'F' : 'S', colors[state->color]);
watch_display_string(buf, 0);
}
bool blinky_face_loop(movement_event_t event, void *context) {
blinky_face_state_t *state = (blinky_face_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
_blinky_face_update_lcd(state);
break;
case EVENT_LIGHT_BUTTON_DOWN:
if (!state->active) {
state->color = (state->color + 1) % 3;
_blinky_face_update_lcd(state);
}
break;
case EVENT_ALARM_BUTTON_UP:
if (!state->active) {
state->active = true;
watch_clear_display();
movement_request_tick_frequency(state->fast ? 8 : 2);
} else {
state->active = false;
watch_set_led_off();
_blinky_face_update_lcd(state);
}
break;
case EVENT_ALARM_LONG_PRESS:
if (!state->active) {
state->fast = !state->fast;
_blinky_face_update_lcd(state);
}
break;
case EVENT_TICK:
if (state->active) {
if (event.subsecond % 2 == 0) watch_set_led_off();
else if (state->color == 0) watch_set_led_red();
else if (state->color == 1) watch_set_led_green();
else watch_set_led_yellow();
}
break;
case EVENT_TIMEOUT:
if (!state->active) movement_move_to_face(0);
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void blinky_face_resign(void *context) {
(void) context;
watch_set_led_off();
}

View File

@@ -0,0 +1,75 @@
/*
* MIT License
*
* 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 BLINKY_FACE_H_
#define BLINKY_FACE_H_
/*
* BLINKY LIGHT face
*
* The blinky light watch face was designed as a tutorial for making a watch
* face in Movement, but it actually might be useful to have a blinking light
* in a pinch.
*
* The screen displays the name of the watch face (”BL”), as well as an S at
* the top right for slow blink or an F for fast blink. The bottom line selects
* the color: green, red or yellow. You can change the speed of the blinking
* light by pressing the Alarm button, and change the color with the Light
* button. A long press on the Alarm button starts the blinking light, and
* another long press stops it.
*
* Note that this will chew through your battery! The green LED uses about
* 450µA at full brightness, which is 45 times the normal power consumption of
* the watch. The red LED is an order of magnitude less efficient (4500 µA),
* and the yellow setting lights both LEDs, which chews through nearly
* 5 milliamperes. This means that one hour of yellow blinking is likely to
* eat up between 2 and 3 percent of the batterys usable life!
*
* Still, if you need to signal your location to someone in a dark forest,
* this watch face could come in handy. Just try to use the green LED as much
* as you can.
*/
#include "movement.h"
typedef struct {
bool active;
bool fast;
uint8_t color;
} blinky_face_state_t;
void blinky_face_setup(uint8_t watch_face_index, void ** context_ptr);
void blinky_face_activate(void *context);
bool blinky_face_loop(movement_event_t event, void *context);
void blinky_face_resign(void *context);
#define blinky_face ((const watch_face_t){ \
blinky_face_setup, \
blinky_face_activate, \
blinky_face_loop, \
blinky_face_resign, \
NULL, \
})
#endif // BLINKY_FACE_H_

View File

@@ -0,0 +1,204 @@
/*
* 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(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) 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(void *context) {
// same as above: silence the warning, we don't need to check the 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 watch_buzzer_note_t 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 watch_buzzer_note_t 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 watch_buzzer_note_t 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 watch_buzzer_note_t 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, void *context) {
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);
break;
}
return true;
}
void breathing_face_resign(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) context;
}

View File

@@ -0,0 +1,54 @@
/*
* 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_
/*
* BOXED BREATHING face
*
* Breathing is a complication for guiding boxed breathing sessions.
* Boxed breathing is a technique to help you stay calm and improve
* concentration in stressful situations.
*
* Usage: Timed messages will cycle as long as this face is active.
* Press ALARM to toggle sound.
*/
#include "movement.h"
void breathing_face_setup(uint8_t watch_face_index, void ** context_ptr);
void breathing_face_activate(void *context);
bool breathing_face_loop(movement_event_t event, void *context);
void breathing_face_resign(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_

View File

@@ -0,0 +1,464 @@
/*
* MIT License
*
* Copyright (c) 2023 Hugo Chargois
*
* 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 "butterfly_game_face.h"
static char butterfly_shapes[][3] = {
"[]", "][", "25", "52", "9e", "e9", "6a", "a6", "3E", "E3", "00", "HH", "88"
};
static int8_t single_beep[] = {BUZZER_NOTE_A7, 4, 0};
static int8_t round_win_melody[] = {
BUZZER_NOTE_C6, 4,
BUZZER_NOTE_E6, 4,
BUZZER_NOTE_G6, 4,
BUZZER_NOTE_C7, 12,
0};
static int8_t round_lose_melody[] = {
BUZZER_NOTE_E6, 4,
BUZZER_NOTE_F6, 4,
BUZZER_NOTE_D6SHARP_E6FLAT, 4,
BUZZER_NOTE_C6, 12,
0};
static int8_t game_win_melody[] = {
BUZZER_NOTE_G6, 4,
BUZZER_NOTE_A6, 4,
BUZZER_NOTE_B6, 4,
BUZZER_NOTE_C7, 12,
BUZZER_NOTE_D7, 4,
BUZZER_NOTE_E7, 4,
BUZZER_NOTE_D7, 4,
BUZZER_NOTE_C7, 12,
BUZZER_NOTE_B6, 4,
BUZZER_NOTE_C7, 4,
BUZZER_NOTE_D7, 4,
BUZZER_NOTE_G7, 24,
0};
#define NUM_SHAPES (sizeof(butterfly_shapes) / sizeof(butterfly_shapes[0]))
#define POS_LEFT 4
#define POS_CENTER 6
#define POS_RIGHT 8
#define TICK_FREQ 8
#define TICKS_PER_SHAPE 8
#define PLAYER_1 0
#define PLAYER_2 1
// returns a random integer r with 0 <= r < max
static inline uint8_t _get_rand(uint8_t max) {
#if __EMSCRIPTEN__
return rand() % max;
#else
return arc4random_uniform(max);
#endif
}
/*
* The game is built with a simple state machine where each state is called a
* "screen". Each screen can draw on the display and handles events, including
* the "activate" event, which is repurposed and sent whenever we move from one
* screen to another via the _transition_to function. Basically it's a mini
* movement inside movement.
*/
typedef bool (*screen_fn_t)(movement_event_t, butterfly_game_state_t*);
static screen_fn_t cur_screen_fn;
static bool _transition_to(screen_fn_t sf, butterfly_game_state_t *state) {
movement_event_t ev = {EVENT_ACTIVATE, 0};
cur_screen_fn = sf;
return sf(ev, state);
}
static uint8_t _pick_wrong_shape(butterfly_game_state_t *state, bool skip_wrong_shape) {
if (!skip_wrong_shape) {
// easy case, we only need to skip over 1 shape: the correct shape
uint8_t r = _get_rand(NUM_SHAPES-1);
if (r >= state->correct_shape) {
r++;
}
return r;
} else {
// a bit more complex, we need to skip over 2 shapes: the correct one
// and the current wrong one
uint8_t r = _get_rand(NUM_SHAPES-2);
uint8_t i1, i2; // the 2 indices to skip over, with i1 < i2
if (state->correct_shape < state->current_shape) {
i1 = state->correct_shape;
i2 = state->current_shape;
} else {
i1 = state->current_shape;
i2 = state->correct_shape;
}
if (r >= i1) {
r++;
}
if (r >= i2) {
r++;
}
return r;
}
}
static void _display_shape(uint8_t shape, uint8_t pos) {
watch_display_string(butterfly_shapes[shape], pos);
}
static void _display_scores(butterfly_game_state_t *state) {
char buf[] = " ";
buf[0] = '0' + state->score_p1;
watch_display_string(buf, 0);
buf[0] = '0' + state->score_p2;
watch_display_string(buf, 3);
}
static void _play_sound(butterfly_game_state_t *state, int8_t *seq) {
if (state->sound) watch_buzzer_play_sequence(seq, NULL);
}
static bool _round_start_screen(movement_event_t event, butterfly_game_state_t *state);
static bool _reset_screen(movement_event_t event, butterfly_game_state_t *state);
static bool _game_win_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = 4 * TICK_FREQ;
watch_clear_display();
if (state->score_p1 >= state->goal_score) {
watch_display_string("pl1 wins", 0);
} else {
watch_display_string("pl2 wins", 0);
}
_play_sound(state, game_win_melody);
break;
case EVENT_TICK:
state->ctr--;
if (state->ctr == 0) {
return _transition_to(_reset_screen, state);
}
break;
}
return true;
}
static bool _round_win_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = TICK_FREQ;
if (state->round_winner == PLAYER_1) {
state->score_p1++;
} else {
state->score_p2++;
}
watch_clear_display();
_display_scores(state);
_display_shape(state->correct_shape, state->round_winner == PLAYER_1 ? POS_LEFT : POS_RIGHT);
_play_sound(state, round_win_melody);
break;
case EVENT_TICK:
state->ctr--;
if (state->ctr == 0) {
if (state->score_p1 >= state->goal_score || state->score_p2 >= state->goal_score) {
return _transition_to(_game_win_screen, state);
}
return _transition_to(_round_start_screen, state);
}
break;
}
return true;
}
static bool _round_lose_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = TICK_FREQ;
if (state->round_winner == PLAYER_1) {
if (state->score_p2 > 0) state->score_p2--;
} else {
if (state->score_p1 > 0) state->score_p1--;
}
_display_shape(state->correct_shape, POS_CENTER);
_play_sound(state, round_lose_melody);
break;
case EVENT_TICK:
if (--state->ctr == 0) {
return _transition_to(_round_start_screen, state);
}
_display_shape(state->ctr%2 ? state->correct_shape : state->current_shape, POS_CENTER);
break;
}
return true;
}
static bool _correct_shape_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
_display_shape(state->correct_shape, POS_CENTER);
_play_sound(state, single_beep);
break;
case EVENT_LIGHT_BUTTON_DOWN:
state->round_winner = PLAYER_1;
return _transition_to(_round_win_screen, state);
case EVENT_ALARM_BUTTON_DOWN:
state->round_winner = PLAYER_2;
return _transition_to(_round_win_screen, state);
}
return true;
}
static bool _wrong_shape_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = TICKS_PER_SHAPE;
state->current_shape = _pick_wrong_shape(state, true);
_display_shape(state->current_shape, POS_CENTER);
_play_sound(state, single_beep);
break;
case EVENT_TICK:
if (--state->ctr == 0) {
if (--state->show_correct_shape_after == 0) {
return _transition_to(_correct_shape_screen, state);
}
return _transition_to(_wrong_shape_screen, state);
}
break;
case EVENT_LIGHT_BUTTON_DOWN:
state->round_winner = PLAYER_2;
return _transition_to(_round_lose_screen, state);
case EVENT_ALARM_BUTTON_DOWN:
state->round_winner = PLAYER_1;
return _transition_to(_round_lose_screen, state);
}
return true;
}
static bool _first_wrong_shape_screen(movement_event_t event, butterfly_game_state_t *state) {
// the first of the wrong shape screens is a bit different than the next
// ones, for 2 reasons:
// * we can pick any shape except one (the correct shape); whereas in the
// subsequent wrong shape screens, we also must not pick the same wrong
// shape as the last
// * we don't act on the light/alarm button events; they would normally be
// a fail in a wrong shape screen, but in this case it may just be that
// the 2 players acknowledge the picked shape (in the previous screen) in
// quick succession, and we don't want the second player to immediately
// fail.
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = TICKS_PER_SHAPE;
state->current_shape = _pick_wrong_shape(state, false);
_display_shape(state->current_shape, POS_CENTER);
_play_sound(state, single_beep);
break;
case EVENT_TICK:
if (--state->ctr == 0) {
return _transition_to(_wrong_shape_screen, state);
}
break;
}
return true;
}
static bool _round_start_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->correct_shape = _get_rand(NUM_SHAPES);
state->show_correct_shape_after = _get_rand(10) + 1;
watch_display_string(" - -", 0);
_display_scores(state);
_display_shape(state->correct_shape, POS_CENTER);
break;
case EVENT_LIGHT_BUTTON_DOWN:
case EVENT_ALARM_BUTTON_DOWN:
watch_display_string(" ", 4);
return _transition_to(_first_wrong_shape_screen, state);
}
return true;
}
static bool _goal_select_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
watch_clear_display();
state->goal_score = 6;
break;
case EVENT_LIGHT_BUTTON_DOWN:
return _transition_to(_round_start_screen, state);
case EVENT_ALARM_BUTTON_DOWN:
state->goal_score += 3;
if (state->goal_score > 9) state->goal_score = 3;
break;
}
char buf[] = "GOaL ";
buf[5] = '0' + state->goal_score;
watch_display_string(buf, 4);
return true;
}
static bool _reset_screen(movement_event_t event, butterfly_game_state_t *state) {
(void) event;
state->score_p1 = 0;
state->score_p2 = 0;
return _transition_to(_goal_select_screen, state);
}
static bool _continue_select_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
watch_clear_display();
// no game in progress, start a new game
if (state->score_p1 == 0 && state->score_p2 == 0) {
return _transition_to(_goal_select_screen, state);
}
state->cont = false;
break;
case EVENT_LIGHT_BUTTON_DOWN:
if (state->cont) {
return _transition_to(_round_start_screen, state);
}
return _transition_to(_reset_screen, state);
case EVENT_ALARM_BUTTON_DOWN:
state->cont = !state->cont;
break;
}
if (state->cont) {
watch_display_string("Cont y", 4);
} else {
watch_display_string("Cont n", 4);
}
return true;
}
static bool _sound_select_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
watch_clear_display();
break;
case EVENT_LIGHT_BUTTON_DOWN:
return _transition_to(_continue_select_screen, state);
case EVENT_ALARM_BUTTON_DOWN:
state->sound = !state->sound;
break;
}
if (state->sound) {
watch_display_string("snd y", 5);
} else {
watch_display_string("snd n", 5);
}
return true;
}
static bool _splash_screen(movement_event_t event, butterfly_game_state_t *state) {
switch (event.event_type) {
case EVENT_ACTIVATE:
state->ctr = TICK_FREQ;
watch_clear_display();
watch_display_string("Btrfly", 4);
break;
case EVENT_LIGHT_BUTTON_DOWN:
case EVENT_ALARM_BUTTON_DOWN:
return _transition_to(_sound_select_screen, state);
case EVENT_TICK:
if (--state->ctr == 0) {
return _transition_to(_sound_select_screen, state);
}
break;
}
return true;
}
void butterfly_game_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(butterfly_game_state_t));
memset(*context_ptr, 0, sizeof(butterfly_game_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.
#if __EMSCRIPTEN__
// simulator only: seed the random number generator
time_t t;
srand((unsigned) time(&t));
#endif
}
void butterfly_game_face_activate(void *context) {
(void) context;
movement_request_tick_frequency(TICK_FREQ);
}
bool butterfly_game_face_loop(movement_event_t event, void *context) {
butterfly_game_state_t *state = (butterfly_game_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
return _transition_to(_splash_screen, state);
case EVENT_TICK:
case EVENT_LIGHT_BUTTON_DOWN:
case EVENT_ALARM_BUTTON_DOWN:
return (*cur_screen_fn)(event, state);
case EVENT_TIMEOUT:
movement_move_to_face(0);
return true;
default:
return movement_default_loop_handler(event);
}
}
void butterfly_game_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 2023 Hugo Chargois
*
* 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 BUTTERFLY_GAME_FACE_H_
#define BUTTERFLY_GAME_FACE_H_
#include "movement.h"
/*
* BUTTERFLY
*
* A GAME OF SHAPE RECOGNITION AND QUICK REFLEXES FOR 2 PLAYERS
*
* Setup
* =====
*
* The game is played by 2 players, each using a distinct button:
* - player 1 plays with the LIGHT (upper left) button
* - player 2 plays with the ALARM (lower right) button
*
* To play, both players need a firm grip on the watch. A suggested method is to
* face each other, remove the watch from the wrist, and position it sideways
* between you. Hold one side of the strap in your preferred hand (right or
* left) and use your thumb to play.
*
* Start of the game
* =================
*
* After the splash screen (BtrFly) is shown, the game proceeds through a couple
* configuration screens. Use ALARM to cycle through the possible values, and
* LIGHT to validate and move to the next screen.
*
* The configuration options are:
*
* - snd y/n Toggle sound effects on or off
* - goal 3/6/9 Choose to play a game of 3, 6 or 9 points
* - cont y/n Decide to continue an unfinished game or start a new one
* (this option appears only if a game is in progress)
*
* Rules
* =====
*
* Prior to each round, a symmetrical shape composed of 2 characters is shown in
* the center of the screen. This shape, representing a butterfly's wings, is
* randomly chosen from a set of a dozen or so possible shapes. For example:
*
* ][
*
* Memorize this shape! Your objective in the round will be to "catch" this
* "butterfly" by pressing your button before your opponent does.
*
* Once you believe you've memorized the shape, press your button. The round
* officially begins as soon as either player presses their button.
*
* Various "butterflies" will then appear on the screen, one after the other.
* The fastest player to press their button when the correct butterfly is shown
* wins the round. However, if a player presses their button when an incorrect
* butterfly is shown, they immediately lose the round.
*
* Scoring
* =======
*
* The scores are displayed at the top of the screen at all times.
*
* When a round is won by a player, their score increases by one. When a round
* is lost by a player, their score decreases by one; unless they have a score
* of 0, in which case it remains unchanged.
*
* The game ends when a player reaches the set point goal (3, 6 or 9 points).
*
*/
typedef struct {
bool cont : 1; // continue
bool sound : 1;
uint8_t goal_score : 4;
// a generic ctr used by multiple states to display themselves for multiple frames
uint8_t ctr : 6;
uint8_t correct_shape : 5;
uint8_t current_shape : 5;
uint8_t show_correct_shape_after : 5;
uint8_t round_winner : 1;
uint8_t score_p1 : 5;
uint8_t score_p2 : 5;
} butterfly_game_state_t;
void butterfly_game_face_setup(uint8_t watch_face_index, void ** context_ptr);
void butterfly_game_face_activate(void *context);
bool butterfly_game_face_loop(movement_event_t event, void *context);
void butterfly_game_face_resign(void *context);
#define butterfly_game_face ((const watch_face_t){ \
butterfly_game_face_setup, \
butterfly_game_face_activate, \
butterfly_game_face_loop, \
butterfly_game_face_resign, \
NULL, \
})
#endif // BUTTERFLY_GAME_FACE_H_

View File

@@ -0,0 +1,264 @@
/*
* MIT License
*
* Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech>
*
* 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 "couch_to_5k_face.h"
// They go: Warmup, Run, Walk, Run, Walk, Run, Walk ... , End (0)
// Time is defined in seconds
// Maybe do /10 to reduce memory usage?
// (i don't want to use floats)
// uint16_t C25K_WEEK_TEST[] = {10, 10, 10, 0};
uint16_t C25K_WEEK_1[] = {300, 60, 90, 60, 90, 60, 90, 60, 90, 60, 90, 60,
90, 60, 90, 60, 90, 0};
uint16_t C25K_WEEK_2[] = {300, 90, 120, 90, 120, 90, 120, 90, 120, 90, 120,
90, 120, 0};
uint16_t C25K_WEEK_3[] = {300, 90, 90, 180, 180, 90, 90, 180, 180, 0};
uint16_t C25K_WEEK_4[] = {300, 180, 90, 300, 150, 180, 90, 300, 0};
uint16_t C25K_WEEK_5_1[] = {300, 300, 180, 300, 180, 300, 0 };
uint16_t C25K_WEEK_5_2[] = {300, 480, 300, 480 , 0};
uint16_t C25K_WEEK_5_3[] = {300, 1200, 0};
uint16_t C25K_WEEK_6_1[] = {300, 300, 180, 480, 180, 300, 0 };
uint16_t C25K_WEEK_6_2[] = {300, 600, 180, 600 , 0};
uint16_t C25K_WEEK_6_3[] = {300, 1500, 0};
uint16_t C25K_WEEK_7[] = {300, 1500, 0};
uint16_t C25K_WEEK_8[] = {300, 1680, 0};
uint16_t C25K_WEEK_9[] = {300, 1800, 0};
#define C25K_SESSIONS_LENGTH 3*9
uint16_t *C25K_SESSIONS[C25K_SESSIONS_LENGTH];
static inline bool _finished(couch_to_5k_state_t *state){
return state->exercise_type == C25K_FINISHED;
}
static inline bool _cleared(couch_to_5k_state_t *state){
return state->timer == C25K_SESSIONS[state->session][0]
&& state->exercise == 0;
}
static inline void _next_session(couch_to_5k_state_t *state){
if (++state->session >= C25K_SESSIONS_LENGTH){
state->session = 0;
}
}
static inline void _assign_exercise_type(couch_to_5k_state_t *state){
if (state->exercise == 0){
state->exercise_type = C25K_WARMUP;
} else if (state->exercise % 2 == 1){
state->exercise_type = C25K_RUN;
} else {
state->exercise_type = C25K_WALK;
}
}
static void _next_exercise(couch_to_5k_state_t *state){
state->exercise++;
state->timer = C25K_SESSIONS[state->session][state->exercise];
// If the new timer starts in zero, it's finished
if (state->timer == 0){
movement_play_alarm_beeps(7, BUZZER_NOTE_C8);
state->exercise_type = C25K_FINISHED;
return;
}
movement_play_alarm_beeps(4, BUZZER_NOTE_A7);
_assign_exercise_type(state);
}
static void _init_session(couch_to_5k_state_t *state){
state->exercise = 0; // Restart exercise counter
state->timer = C25K_SESSIONS[state->session][state->exercise];
_assign_exercise_type(state);
}
static char *_exercise_type_to_str(exercise_type_t t){
switch (t){
case C25K_WARMUP:
return "WU";
case C25K_RUN:
return "RU";
case C25K_WALK:
return "WA";
case C25K_FINISHED:
return "--";
default:
return " ";
}
}
static void _display(couch_to_5k_state_t *state, char *buf){
// TODO only repaint needed parts
uint8_t seconds = state->timer % 60;
sprintf(buf, "%s%2d%2d%02d%02d",
_exercise_type_to_str(state->exercise_type),
(state->session + 1) % 100,
((state->timer - seconds) / 60) % 100,
seconds,
(state->exercise + 1) % 100);
watch_display_string(buf, 0);
}
void couch_to_5k_face_setup(uint8_t
watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(couch_to_5k_state_t));
memset(*context_ptr, 0, sizeof(couch_to_5k_state_t));
// Do any one-time tasks in here; the inside of this conditional
// happens only at boot.
// C25K_SESSIONS[0] = C25K_WEEK_TEST;
C25K_SESSIONS[0] = C25K_WEEK_1;
C25K_SESSIONS[1] = C25K_WEEK_1;
C25K_SESSIONS[2] = C25K_WEEK_1;
C25K_SESSIONS[3] = C25K_WEEK_2;
C25K_SESSIONS[4] = C25K_WEEK_2;
C25K_SESSIONS[5] = C25K_WEEK_2;
C25K_SESSIONS[6] = C25K_WEEK_3;
C25K_SESSIONS[7] = C25K_WEEK_3;
C25K_SESSIONS[8] = C25K_WEEK_3;
C25K_SESSIONS[9] = C25K_WEEK_4;
C25K_SESSIONS[10] = C25K_WEEK_4;
C25K_SESSIONS[11] = C25K_WEEK_4;
C25K_SESSIONS[12] = C25K_WEEK_5_1;
C25K_SESSIONS[13] = C25K_WEEK_5_2;
C25K_SESSIONS[14] = C25K_WEEK_5_3;
C25K_SESSIONS[15] = C25K_WEEK_6_1;
C25K_SESSIONS[16] = C25K_WEEK_6_2;
C25K_SESSIONS[17] = C25K_WEEK_6_3;
C25K_SESSIONS[18] = C25K_WEEK_7;
C25K_SESSIONS[19] = C25K_WEEK_7;
C25K_SESSIONS[20] = C25K_WEEK_7;
C25K_SESSIONS[21] = C25K_WEEK_8;
C25K_SESSIONS[22] = C25K_WEEK_8;
C25K_SESSIONS[23] = C25K_WEEK_8;
C25K_SESSIONS[24] = C25K_WEEK_9;
C25K_SESSIONS[25] = C25K_WEEK_9;
C25K_SESSIONS[26] = C25K_WEEK_9;
}
// Do any pin or peripheral setup here; this will be called whenever the
// watch wakes from deep sleep.
}
void couch_to_5k_face_activate(void *context) {
(void) context;
// Handle any tasks related to your watch face coming on screen.
watch_set_colon();
}
bool couch_to_5k_face_loop(movement_event_t event,
void *context) {
couch_to_5k_state_t *state = (couch_to_5k_state_t *)context;
static char buf[11];
static bool paused = true;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Show your initial UI here.
movement_request_tick_frequency(1);
_init_session(state);
paused = true;
_display(state, buf);
break;
case EVENT_TICK:
if ( !paused && !_finished(state) ) {
if (state->timer == 0){
_next_exercise(state);
} else {
state->timer--;
}
}
_display(state, buf);
break;
case EVENT_LIGHT_BUTTON_UP:
// This is the next-exercise / reset button.
// When finished move to the next session and leave it paused
if ( _finished(state) ){
_next_session(state);
_init_session(state);
paused = true;
break;
}
// When paused and cleared move to next, when only paused, clear
if ( paused ) {
if ( _cleared(state) ){
_next_session(state);
}
_init_session(state);
}
break;
case EVENT_ALARM_BUTTON_UP:
if (movement_button_should_sound()) {
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
}
paused = !paused;
break;
case EVENT_TIMEOUT:
// Your watch face will receive this event after a period of
// inactivity. If it makes sense to resign,
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_sleep_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);
}
// 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 couch_to_5k_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,87 @@
/*
* MIT License
*
* Copyright (c) 2023 Ekaitz Zarraga <ekaitz@elenq.tech>
*
* 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 COUCHTO5K_FACE_H_
#define COUCHTO5K_FACE_H_
#include "movement.h"
/*
* Couch To 5k;
*
*
* The program is designed to train 3 times a week. Each training is a
* *session*. Each of the rounds you have in the training is an *exercise*.
*
* The training goes like this:
* 5min warm-up walk -> Run X minutes -> Walk Y minutes -> ... -> Stop
*
* The watch face shows it like this: The weekday indicator shows if you need
* to Warm Up (`WU`), run (`rU`), walk (`WA`) or stop (`--`).
*
* The month-day indicator shows the session you are in (from 1 to 27).
*
* The timer shows the time you have left in the exercise and the exercise you
* are doing (MM:SS:ee). When an exercise finishes you are notified with an
* alarm. When the whole session finishes, a different tone is played for a
* longer period.
*
* Pressing the ALARM button pauses/resumes the clock.
*
* Pressing the LIGHT button does nothing if the timer is not paused. When it
* is paused it clears the current session (it restarts it to the beginning)
* and if it was already cleared or the current session was finished moves to
* the next session.
*/
typedef enum {
C25K_WARMUP,
C25K_RUN,
C25K_WALK,
C25K_FINISHED
} exercise_type_t;
typedef struct {
// Anything you need to keep track of, put it here!
uint8_t session;
uint8_t exercise;
exercise_type_t exercise_type;
uint16_t timer;
} couch_to_5k_state_t;
void couch_to_5k_face_setup(uint8_t watch_face_index, void ** context_ptr);
void couch_to_5k_face_activate(void *context);
bool couch_to_5k_face_loop(movement_event_t event, void *context);
void couch_to_5k_face_resign(void *context);
#define couch_to_5k_face ((const watch_face_t){ \
couch_to_5k_face_setup, \
couch_to_5k_face_activate, \
couch_to_5k_face_loop, \
couch_to_5k_face_resign, \
NULL, \
})
#endif // COUCHTO5K_FACE_H_

View File

@@ -0,0 +1,145 @@
/*
* MIT License
*
* Copyright (c) 2022 Shogo Okamoto
*
* 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 "counter_face.h"
#include "watch.h"
void counter_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(counter_state_t));
memset(*context_ptr, 0, sizeof(counter_state_t));
counter_state_t *state = (counter_state_t *)*context_ptr;
state->beep_on = true;
}
}
void counter_face_activate(void *context) {
counter_state_t *state = (counter_state_t *)context;
if (state->beep_on) {
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
}
}
bool counter_face_loop(movement_event_t event, void *context) {
counter_state_t *state = (counter_state_t *)context;
switch (event.event_type) {
case EVENT_ALARM_BUTTON_UP:
watch_buzzer_abort_sequence(); //abort running buzzer sequence when counting fast
state->counter_idx++; // increment counter index
if (state->counter_idx>99) { //0-99
state->counter_idx=0;//reset counter index
}
print_counter(state);
if (state->beep_on) {
beep_counter(state);
}
break;
case EVENT_LIGHT_LONG_PRESS:
watch_buzzer_abort_sequence();
state->beep_on = !state->beep_on;
if (state->beep_on) {
watch_set_indicator(WATCH_INDICATOR_SIGNAL);
} else {
watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
}
break;
case EVENT_ALARM_LONG_PRESS:
state->counter_idx=0; // reset counter index
print_counter(state);
break;
case EVENT_ACTIVATE:
print_counter(state);
break;
case EVENT_TIMEOUT:
// ignore timeout
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
// beep counter index times
void beep_counter(counter_state_t *state) {
int low_count = state->counter_idx/5;
int high_count = state->counter_idx - low_count * 5;
static int8_t sound_seq[15];
memset(sound_seq, 0, 15);
int i = 0;
if (low_count > 0) {
sound_seq[i] = BUZZER_NOTE_A6;
i++;
sound_seq[i] = 3;
i++;
sound_seq[i] = BUZZER_NOTE_REST;
i++;
sound_seq[i] = 6;
i++;
if (low_count > 1) {
sound_seq[i] = -2;
i++;
sound_seq[i] = low_count-1;
i++;
}
sound_seq[i] = BUZZER_NOTE_REST;
i++;
sound_seq[i] = 6;
i++;
}
if (high_count > 0) {
sound_seq[i] = BUZZER_NOTE_B6;
i++;
sound_seq[i] = 3;
i++;
sound_seq[i] = BUZZER_NOTE_REST;
i++;
sound_seq[i] = 6;
i++;
}
if (high_count > 1) {
sound_seq[i] = -2;
i++;
sound_seq[i] = high_count-1;
}
watch_buzzer_play_sequence((int8_t *)sound_seq, NULL);
}
// print counter index at the center of display.
void print_counter(counter_state_t *state) {
char buf[14];
sprintf(buf, "CO %02d", state->counter_idx); // center of LCD display
watch_display_string(buf, 0);
}
void counter_face_resign(void *context) {
(void) context;
}

View File

@@ -0,0 +1,63 @@
/*
* MIT License
*
* Copyright (c) 2022 Shogo Okamoto
*
* 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 COUNTER_FACE_H_
#define COUNTER_FACE_H_
/*
* COUNTER face
*
* Counter face is designed to count the number of running laps during exercises.
*
* Usage:
* Short-press ALARM to increment the counter (loops at 99)
* Long-press ALARM to reset the counter.
* Long-press LIGHT to toggle sound.
*/
#include "movement.h"
typedef struct {
uint8_t counter_idx;
bool beep_on;
} counter_state_t;
void counter_face_setup(uint8_t watch_face_index, void ** context_ptr);
void counter_face_activate(void *context);
bool counter_face_loop(movement_event_t event, void *context);
void counter_face_resign(void *context);
void print_counter(counter_state_t *state);
void beep_counter(counter_state_t *state);
#define counter_face ((const watch_face_t){ \
counter_face_setup, \
counter_face_activate, \
counter_face_loop, \
counter_face_resign, \
NULL, \
})
#endif // COUNTER_FACE_H_

View File

@@ -0,0 +1,143 @@
/*
* MIT License
*
* Copyright (c) 2022 Mikhail Svarichevsky
*
* 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 "databank_face.h"
#include "watch.h"
#include "watch_private_display.h"
const char *pi_data[] = {
"PI", "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442",
"S ", "9192631770",
"31", "2147483648",
"32", "4294967296",
"63", "9223372036854775808",
"64", "18446744073709551616",
};
//we show 6 characters per screen
const int databank_num_pages = (sizeof(pi_data) / sizeof(char*) / 2);
struct {
uint8_t current_word;
uint8_t databank_page;
bool animating;
} databank_state;
void databank_face_setup(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) context_ptr;
(void) watch_face_index;
// At boot, context_ptr will be NULL indicating that we don't have anyplace to store our context.
}
void databank_face_activate(void *context) {
// same as above: silence the warning, we don't need to check the settings.
(void) context;
// we do however need to set some things in our context. Here we cast it to the correct type...
databank_state.current_word = 0;
databank_state.animating = true;
}
static void display()
{
char buf[14];
int page = databank_state.current_word;
sprintf(buf, "%s%2d", pi_data[databank_state.databank_page * 2 + 0], page);
watch_display_string(buf, 0);
bool data_ended = false;
for (int i = 0; i < 6; i++) {
if (pi_data[databank_state.databank_page * 2 + 1][page * 6 + i] == 0) {
data_ended = true;
}
if (!data_ended) {
watch_display_character(pi_data[databank_state.databank_page * 2 + 1][page * 6 + i], 4 + i);
} else {
// only 6 digits per page
watch_display_character(' ', 4 + i);
}
}
}
bool databank_face_loop(movement_event_t event, void *context) {
(void) context;
int max_words = (strlen(pi_data[databank_state.databank_page * 2 + 1]) - 1) / 6 + 1;
switch (event.event_type) {
case EVENT_ACTIVATE:
display();
case EVENT_TICK:
break;
case EVENT_LIGHT_BUTTON_UP:
databank_state.current_word = (databank_state.current_word + max_words - 1) % max_words;
display();
break;
case EVENT_LIGHT_LONG_PRESS:
databank_state.databank_page = (databank_state.databank_page + databank_num_pages - 1) % databank_num_pages;
databank_state.current_word = 0;
display();
break;
case EVENT_ALARM_LONG_PRESS:
databank_state.databank_page = (databank_state.databank_page + 1) % databank_num_pages;
databank_state.current_word = 0;
display();
break;
case EVENT_ALARM_BUTTON_UP:
databank_state.current_word = (databank_state.current_word + 1) % max_words;
display();
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;
case EVENT_LIGHT_BUTTON_DOWN:
// don't light up every time light is hit
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void databank_face_resign(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) context;
}

View File

@@ -0,0 +1,60 @@
/*
* MIT License
*
* 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 DATABANK_FACE_H_
#define DATABANK_FACE_H_
/*
* DATABANK face
*
* Displays some pre-defined data that you might want to remember
* Math constants, birthdays, phone numbers...
*
* Usage: Edit the global variable `pi_data` in "databank_face.c"
* to the define the data that will be displayed. Each "item" contains
* a two-letter label (using the day-of-week display), then a longer
* string that will be displayed one "word" (six characters) at a time.
*
* Short-press ALARM to display the next word.
* Short-press LIGHT to display the previous word.
* Long-press ALARM to display the next item.
* Long-press LIGHT to display the previous item.
*/
#include "movement.h"
void databank_face_setup(uint8_t watch_face_index, void ** context_ptr);
void databank_face_activate(void *context);
bool databank_face_loop(movement_event_t event, void *context);
void databank_face_resign(void *context);
#define databank_face ((const watch_face_t){ \
databank_face_setup, \
databank_face_activate, \
databank_face_loop, \
databank_face_resign, \
NULL, \
})
#endif // DATABANK_FACE_H_

View File

@@ -0,0 +1,270 @@
/*
* MIT License
*
* 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.
*/
#include <stdlib.h>
#include <string.h>
#include "day_one_face.h"
#include "watch.h"
#include "watch_utility.h"
static uint32_t _day_one_face_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;
}
static void _day_one_face_update(day_one_state_t *state) {
char buf[15];
watch_date_time_t 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);
if (julian_date < julian_birthdate) {
sprintf(buf, "DA %6lu", julian_birthdate - julian_date);
} else {
sprintf(buf, "DA %6lu", julian_date - julian_birthdate);
}
watch_display_string(buf, 0);
}
static void _day_one_face_abort_quick_cycle(day_one_state_t *state) {
if (state->quick_cycle) {
state->quick_cycle = false;
movement_request_tick_frequency(4);
}
}
static void _day_one_face_increment(day_one_state_t *state) {
state->birthday_changed = true;
switch (state->current_page) {
case PAGE_YEAR:
state->birth_year = state->birth_year + 1;
if (state->birth_year > 2080) state->birth_year = 1900;
break;
case PAGE_MONTH:
state->birth_month = (state->birth_month % 12) + 1;
break;
case PAGE_DAY:
state->birth_day = (state->birth_day % watch_utility_days_in_month(state->birth_month, state->birth_year)) + 1;
break;
default:
break;
}
}
void day_one_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(day_one_state_t));
memset(*context_ptr, 0, sizeof(day_one_state_t));
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
if (movement_birthdate.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 year 2080. 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.
movement_birthdate.bit.year = 1959;
movement_birthdate.bit.month = 1;
movement_birthdate.bit.day = 1;
watch_store_backup_data(movement_birthdate.reg, 2);
}
}
}
void day_one_face_activate(void *context) {
day_one_state_t *state = (day_one_state_t *)context;
state->current_page = PAGE_DISPLAY;
state->quick_cycle = false;
state->ticks = 0;
// fetch the user's birth date from the birthday register.
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
state->birth_year = movement_birthdate.bit.year;
state->birth_month = movement_birthdate.bit.month;
state->birth_day = movement_birthdate.bit.day;
}
bool day_one_face_loop(movement_event_t event, void *context) {
day_one_state_t *state = (day_one_state_t *)context;
char buf[9];
switch (event.event_type) {
case EVENT_ACTIVATE:
_day_one_face_update(state);
break;
case EVENT_LOW_ENERGY_UPDATE:
case EVENT_TICK:
if (state->quick_cycle) {
if (HAL_GPIO_BTN_ALARM_read()) {
_day_one_face_increment(state);
} else {
_day_one_face_abort_quick_cycle(state);
}
}
switch (state->current_page) {
// if in settings mode, update whatever the current page is
case PAGE_YEAR:
watch_display_string("YR ", 0);
if (event.subsecond % 2) {
sprintf(buf, "%4d", state->birth_year);
watch_display_string(buf, 4);
}
break;
case PAGE_MONTH:
watch_display_string("MO ", 0);
if (event.subsecond % 2) {
sprintf(buf, "%2d", state->birth_month);
watch_display_string(buf, 4);
}
break;
case PAGE_DAY:
watch_display_string("DA ", 0);
if (event.subsecond % 2) {
sprintf(buf, "%2d", state->birth_day);
watch_display_string(buf, 6);
}
break;
// otherwise, check if we have to update. the display only needs to change at midnight!
case PAGE_DISPLAY: {
watch_date_time_t date_time = watch_rtc_get_date_time();
if (date_time.unit.hour == 0 && date_time.unit.minute == 0 && date_time.unit.second == 0) {
_day_one_face_update(state);
}
break;}
case PAGE_DATE:
if (state->ticks > 0) {
state->ticks--;
} else {
state->current_page = PAGE_DISPLAY;
_day_one_face_update(state);
}
break;
default:
break;
}
break;
case EVENT_LIGHT_BUTTON_DOWN:
// only illuminate if we're in display mode
switch (state->current_page) {
case PAGE_DISPLAY:
// fall through
case PAGE_DATE:
movement_illuminate_led();
break;
default:
break;
}
break;
case EVENT_LIGHT_BUTTON_UP:
// otherwise use the light button to advance settings pages.
switch (state->current_page) {
case PAGE_YEAR:
// fall through
case PAGE_MONTH:
// fall through
case PAGE_DAY:
// go to next setting page...
state->current_page = (state->current_page + 1) % 4;
if (state->current_page == PAGE_DISPLAY) {
// ...unless we've been pushed back to display mode.
movement_request_tick_frequency(1);
// force display since it normally won't update til midnight.
_day_one_face_update(state);
}
break;
default:
break;
}
break;
case EVENT_ALARM_BUTTON_UP:
// if we are on a settings page, increment whatever value we're setting.
switch (state->current_page) {
case PAGE_YEAR:
// fall through
case PAGE_MONTH:
// fall through
case PAGE_DAY:
_day_one_face_abort_quick_cycle(state);
_day_one_face_increment(state);
break;
case PAGE_DISPLAY:
state->current_page = PAGE_DATE;
sprintf(buf, "%04d%02d%02d", state->birth_year % 10000, state->birth_month % 100, state->birth_day % 100);
watch_display_string(buf, 2);
state->ticks = 2;
break;
default:
break;
}
break;
case EVENT_ALARM_LONG_PRESS:
// if we aren't already in settings mode, put us there.
switch (state->current_page) {
case PAGE_DISPLAY:
state->current_page++;
movement_request_tick_frequency(4);
break;
case PAGE_YEAR:
// fall through
case PAGE_MONTH:
// fall through
case PAGE_DAY:
state->quick_cycle = true;
movement_request_tick_frequency(8);
break;
default:
break;
}
break;
case EVENT_ALARM_LONG_UP:
_day_one_face_abort_quick_cycle(state);
break;
case EVENT_TIMEOUT:
_day_one_face_abort_quick_cycle(state);
// return home if we're on a settings page (this saves our changes when we resign).
if (state->current_page != PAGE_DISPLAY) {
movement_move_to_face(0);
}
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}
void day_one_face_resign(void *context) {
day_one_state_t *state = (day_one_state_t *)context;
// if the user changed their birth date, store it to the birth date register
if (state->birthday_changed) {
day_one_state_t *state = (day_one_state_t *)context;
movement_birthdate_t movement_birthdate = (movement_birthdate_t) watch_get_backup_data(2);
movement_birthdate.bit.year = state->birth_year;
movement_birthdate.bit.month = state->birth_month;
movement_birthdate.bit.day = state->birth_day;
watch_store_backup_data(movement_birthdate.reg, 2);
state->birthday_changed = false;
}
}

View File

@@ -0,0 +1,83 @@
/*
* MIT License
*
* 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 DAY_ONE_FACE_H_
#define DAY_ONE_FACE_H_
/*
* DAY ONE face
*
* This watch face displays the number of days since or until a given date.
* It was originally designed to display the number of days youve been alive,
* but technically it can count up from any date in the 20th century or the
* 21st century, so far.
*
* Long press on the Alarm button to enter customization mode. The text “YR”
* will appear, and will allow you to set the year starting from 1959. Press
* Alarm repeatedly to advance the year. If your birthday is before 1959,
* advance beyond the current year and it will wrap around to 1900.
*
* Once you have set the year, press Light to set the month (“MO”) and
* day (“DA”), advancing the value by pressing Alarm repeatedly.
*
* Note that at this time, the Day One face does not display the sleep
* indicator in sleep mode, which may make the watch appear to be
* unresponsive in sleep mode. You can still press the Alarm button to
* wake the watch. This UI quirk will be addressed in a future update.
*/
#include "movement.h"
typedef enum {
PAGE_DISPLAY,
PAGE_YEAR,
PAGE_MONTH,
PAGE_DAY,
PAGE_DATE
} day_one_page_t;
typedef struct {
day_one_page_t current_page;
uint16_t birth_year;
uint8_t birth_month;
uint8_t birth_day;
bool birthday_changed;
bool quick_cycle;
uint8_t ticks;
} day_one_state_t;
void day_one_face_setup(uint8_t watch_face_index, void ** context_ptr);
void day_one_face_activate(void *context);
bool day_one_face_loop(movement_event_t event, void *context);
void day_one_face_resign(void *context);
#define day_one_face ((const watch_face_t){ \
day_one_face_setup, \
day_one_face_activate, \
day_one_face_loop, \
day_one_face_resign, \
NULL, \
})
#endif // DAY_ONE_FACE_H_

View File

@@ -0,0 +1,626 @@
/*
* MIT License
*
* Copyright (c) 2023-2024 Konrad Rieck
*
* 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.
*/
/*
* # Deadline Face
*
* This is a watch face for tracking deadlines. It draws inspiration from
* other watch faces of the project but focuses on keeping track of
* deadlines. You can enter and monitor up to four different deadlines by
* providing their respective date and time. The face has two modes:
* *running mode* and *settings mode*.
*
* ## Running Mode
*
* When the watch face is activated, it defaults to running mode. The top
* right corner shows the current deadline number, and the main display
* presents the time left until the deadline. The format of the display
* varies depending on the remaining time.
*
* - When less than a day is left, the display shows the remaining hours,
* minutes, and seconds in the form `HH:MM:SS`.
*
* - When less than a month is left, the display shows the remaining days
* and hours in the form `DD:HH` with the unit `dy` for days.
*
* - When less than a year is left, the display shows the remaining months
* and days in the form `MM:DD` with the unit `mo` for months.
*
* - When more than a year is left, the years and months are displayed in
* the form `YY:MM` with the unit `yr` for years.
*
* - When a deadline has passed in the last 24 hours, the display shows
* `over` to indicate that the deadline has just recently been reached.
*
* - When no deadline is set for a particular slot, or if a deadline has
* already passed by more than 24 hours, `--:--` is displayed.
*
* The user can navigate in running mode using the following buttons:
*
* - The *alarm button* moves the next deadline. There are currently four
* slots available for deadlines. When the last slot has been reached,
* pressing the button moves to the first slot.
*
* - A *long press* on the *alarm button* activates settings mode and
* enables configuring the currently selected deadline.
*
* - A *long press* on the *light button* activates a deadline alarm. The
* bell icon is displayed, and the alarm will ring upon reaching any of
* the deadlines set. It is important to note that the watch will not
* enter low-energy sleep mode while the alarm is enabled.
*
*
* ## Settings Mode
*
* In settings mode, the currently selected slot for a deadline can be
* configured by providing the date and the time. Like running mode, the
* top right corner of the display indicates the current deadline number.
* The main display shows the date and, on the next page, the time to be
* configured.
*
* The user can use the following buttons in settings mode.
*
* - The *light button* navigates through the different date and time
* settings, going from year, month, day, hour, to minute. The selected
* position is blinking.
*
* - A *long press* on the light button resets the date and time to the next
* day at midnight. This is the default deadline.
*
* - The *alarm button* increments the currently selected position. A *long
* press* on the *alarm button* changes the value faster.
*
* - The *mode button* exists setting mode and returns to *running mode*.
* Here the selected deadline slot can be changed.
*
*/
#include <stdlib.h>
#include <string.h>
#include "deadline_face.h"
#include "watch.h"
#include "watch_utility.h"
#define SETTINGS_NUM (5)
const char settings_titles[SETTINGS_NUM][3] = { "YR", "MO", "DA", "HR", "M1" };
/* Local functions */
static void _running_init(deadline_state_t *state);
static bool _running_loop(movement_event_t event, void *context);
static void _running_display(movement_event_t event, deadline_state_t *state);
static void _setting_init(deadline_state_t *state);
static bool _setting_loop(movement_event_t event, void *context);
static void _setting_display(movement_event_t event, deadline_state_t *state, watch_date_time_t date);
/* Utility functions */
static void _background_alarm_play(deadline_state_t *state);
static void _background_alarm_schedule(deadline_state_t *state);
static void _background_alarm_cancel(deadline_state_t *state);
static void _increment_date(deadline_state_t *state, watch_date_time_t date_time);
static inline void _change_tick_freq(uint8_t freq, deadline_state_t *state);
static inline bool _is_leap(int16_t y);
static inline int _days_in_month(int16_t mpnth, int16_t y);
static inline unsigned int _mod(int a, int b);
static inline void _beep_button();
static inline void _beep_enable();
static inline void _beep_disable();
static inline void _reset_deadline(deadline_state_t *state);
/* Check for leap year */
static inline bool _is_leap(int16_t y)
{
y += 1900;
return !(y % 4) && ((y % 100) || !(y % 400));
}
/* Modulo function */
static inline unsigned int _mod(int a, int b)
{
int r = a % b;
return r < 0 ? r + b : r;
}
/* Return days in month */
static inline int _days_in_month(int16_t month, int16_t year)
{
uint8_t days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
month = _mod(month - 1, 12);
if (month == 1 && _is_leap(year)) {
return days[month] + 1;
} else {
return days[month];
}
}
/* Beep for a button press*/
static inline void _beep_button()
{
// Play a beep as confirmation for a button press (if applicable)
if (!movement_button_should_sound())
return;
watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
}
/* Beep for entering settings */
static inline void _beep_enable()
{
if (!movement_button_should_sound())
return;
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 for leaving settings */
static inline void _beep_disable()
{
if (!movement_button_should_sound())
return;
watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
watch_buzzer_play_note(BUZZER_NOTE_G7, 75);
}
/* Change tick frequency */
static inline void _change_tick_freq(uint8_t freq, deadline_state_t *state)
{
if (state->tick_freq != freq) {
movement_request_tick_frequency(freq);
state->tick_freq = freq;
}
}
/* Determine index of closest deadline */
static uint8_t _closest_deadline(deadline_state_t *state)
{
watch_date_time_t now = watch_rtc_get_date_time();
uint32_t now_ts = watch_utility_date_time_to_unix_time(now, movement_get_current_timezone_offset());
uint32_t min_ts = UINT32_MAX;
uint8_t min_index = 0;
for (uint8_t i = 0; i < DEADLINE_FACE_DATES; i++) {
if (state->deadlines[i] < now_ts || state->deadlines[i] > min_ts)
continue;
min_ts = state->deadlines[i];
min_index = i;
}
return min_index;
}
/* Play background alarm */
static void _background_alarm_play(deadline_state_t *state)
{
/* Use the default alarm from movement and move to foreground */
if (state->alarm_enabled) {
movement_play_alarm();
movement_move_to_face(state->face_idx);
}
}
/* Schedule background alarm */
static void _background_alarm_schedule(deadline_state_t *state)
{
/* We simply re-use the scheduling in the background task */
deadline_face_advise(state);
}
/* Cancel background alarm */
static void _background_alarm_cancel(deadline_state_t *state)
{
movement_cancel_background_task_for_face(state->face_idx);
}
/* Reset deadline to tomorrow */
static inline void _reset_deadline(deadline_state_t *state)
{
/* Get current time and reset hours/minutes/seconds */
watch_date_time_t date_time = watch_rtc_get_date_time();
date_time.unit.second = 0;
date_time.unit.minute = 0;
date_time.unit.hour = 0;
/* Add 24 hours to obtain first second of tomorrow */
uint32_t ts = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
ts += 24 * 60 * 60;
state->deadlines[state->current_index] = ts;
}
/* Increment date in settings mode. Function taken from `set_time_face.c` */
static void _increment_date(deadline_state_t *state, watch_date_time_t date_time)
{
const uint8_t days_in_month[12] = { 31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31 };
switch (state->current_page) {
case 0:
/* Only 10 years covered. Fix this sometime next decade */
date_time.unit.year = ((date_time.unit.year % 10) + 1);
break;
case 1:
date_time.unit.month = (date_time.unit.month % 12) + 1;
break;
case 2:
date_time.unit.day = (date_time.unit.day % watch_utility_days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) + 1;
break;
case 3:
date_time.unit.hour = (date_time.unit.hour + 1) % 24;
break;
case 4:
date_time.unit.minute = (date_time.unit.minute + 1) % 60;
break;
}
uint32_t ts = watch_utility_date_time_to_unix_time(date_time, movement_get_current_timezone_offset());
state->deadlines[state->current_index] = ts;
}
/* Update display in running mode */
static void _running_display(movement_event_t event, deadline_state_t *state)
{
(void) event;
/* Seconds, minutes, hours, days, months, years */
int16_t unit[] = { 0, 0, 0, 0, 0, 0 };
uint8_t i, range[] = { 60, 60, 24, 30, 12, 0 };
char buf[16];
/* Display indicators */
if (state->alarm_enabled)
watch_set_indicator(WATCH_INDICATOR_BELL);
else
watch_clear_indicator(WATCH_INDICATOR_BELL);
watch_date_time_t now = watch_rtc_get_date_time();
uint32_t now_ts = watch_utility_date_time_to_unix_time(now, movement_get_current_timezone_offset());
/* Deadline expired */
if (state->deadlines[state->current_index] < now_ts) {
if (state->deadlines[state->current_index] + 24 * 60 * 60 > now_ts)
sprintf(buf, "DL%2dOVER ", state->current_index + 1);
else
sprintf(buf, "DL%2d---- ", state->current_index + 1);
//watch_clear_indicator(WATCH_INDICATOR_BELL);
watch_display_string(buf, 0);
return;
}
/* Get date time structs */
watch_date_time_t deadline = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], movement_get_current_timezone_offset()
);
/* Calculate naive difference of dates */
unit[0] = deadline.unit.second - now.unit.second;
unit[1] = deadline.unit.minute - now.unit.minute;
unit[2] = deadline.unit.hour - now.unit.hour;
unit[3] = deadline.unit.day - now.unit.day;
unit[4] = deadline.unit.month - now.unit.month;
unit[5] = deadline.unit.year - now.unit.year;
/* Correct errors of naive difference */
for (i = 0; i < 6; i++) {
if (unit[i] < 0) {
/* Correct remaining units */
if (i == 3)
unit[i] += _days_in_month(deadline.unit.month - 1, deadline.unit.year);
else
unit[i] += range[i];
/* Carry over change to next unit if non-zero */
if (i < 5 && unit[i + 1] != 0)
unit[i + 1] -= 1;
}
}
/* Set range */
i = state->current_index + 1;
if (unit[5] > 0) {
/* years:months */
sprintf(buf, "DL%2d%02d%02dYR", i, unit[5] % 100, unit[4] % 12);
} else if (unit[4] > 0) {
/* months:days */
sprintf(buf, "DL%2d%02d%02dMO", i, (unit[5] * 12 + unit[4]) % 100, unit[3] % 32);
} else if (unit[3] > 0) {
/* days:hours */
sprintf(buf, "DL%2d%02d%02ddY", i, unit[3] % 32, unit[2] % 24);
} else {
/* hours:minutes:seconds */
sprintf(buf, "DL%2d%02d%02d%02d", i, unit[2] % 24, unit[1] % 60, unit[0] % 60);
}
watch_display_string(buf, 0);
}
/* Init running mode */
static void _running_init(deadline_state_t *state)
{
(void) state;
watch_clear_indicator(WATCH_INDICATOR_24H);
watch_clear_indicator(WATCH_INDICATOR_PM);
watch_set_colon();
/* Ensure 1Hz updates only */
_change_tick_freq(1, state);
}
/* Loop of running mode */
static bool _running_loop(movement_event_t event, void *context)
{
deadline_state_t *state = (deadline_state_t *) context;
if (event.event_type != EVENT_BACKGROUND_TASK)
_running_display(event, state);
switch (event.event_type) {
case EVENT_ALARM_BUTTON_UP:
_beep_button();
state->current_index = (state->current_index + 1) % DEADLINE_FACE_DATES;
_running_display(event, state);
break;
case EVENT_ALARM_LONG_PRESS:
_beep_enable();
_setting_init(state);
state->mode = DEADLINE_FACE_SETTING;
break;
case EVENT_MODE_BUTTON_UP:
movement_move_to_next_face();
return false;
case EVENT_LIGHT_BUTTON_DOWN:
break;
case EVENT_LIGHT_LONG_PRESS:
_beep_button();
state->alarm_enabled = !state->alarm_enabled;
if (state->alarm_enabled) {
_background_alarm_schedule(settings, context);
} else {
_background_alarm_cancel(settings, context);
}
_running_display(event, state);
break;
case EVENT_TIMEOUT:
movement_move_to_face(0);
break;
case EVENT_BACKGROUND_TASK:
_background_alarm_play(state);
break;
case EVENT_LOW_ENERGY_UPDATE:
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
/* Update display in settings mode */
static void _setting_display(movement_event_t event, deadline_state_t *state, watch_date_time_t date_time)
{
char buf[11];
int i = state->current_index + 1;
if (state->current_page > 2) {
watch_set_colon();
if (movement_clock_mode_24h()) {
watch_set_indicator(WATCH_INDICATOR_24H);
sprintf(buf, "%s%2d%2d%02d ", settings_titles[state->current_page], i, date_time.unit.hour, date_time.unit.minute);
} else {
sprintf(buf, "%s%2d%2d%02d ", settings_titles[state->current_page], i, (date_time.unit.hour % 12) ? (date_time.unit.hour % 12) : 12,
date_time.unit.minute);
if (date_time.unit.hour < 12)
watch_clear_indicator(WATCH_INDICATOR_PM);
else
watch_set_indicator(WATCH_INDICATOR_PM);
}
} else {
watch_clear_colon();
watch_clear_indicator(WATCH_INDICATOR_24H);
watch_clear_indicator(WATCH_INDICATOR_PM);
sprintf(buf, "%s%2d%2d%02d%02d", settings_titles[state->current_page], i, date_time.unit.year + 20, date_time.unit.month, date_time.unit.day);
}
/* Blink up the parameter we are setting */
if (event.subsecond % 2) {
switch (state->current_page) {
case 0:
case 3:
buf[4] = buf[5] = ' ';
break;
case 1:
case 4:
buf[6] = buf[7] = ' ';
break;
case 2:
buf[8] = buf[9] = ' ';
break;
}
}
watch_display_string(buf, 0);
}
/* Init setting mode */
static void _setting_init(deadline_state_t *state)
{
state->current_page = 0;
/* Init fresh deadline to next day */
if (state->deadlines[state->current_index] == 0) {
_reset_deadline(state);
}
/* Ensure 1Hz updates only */
_change_tick_freq(1, state);
}
/* Loop of setting mode */
static bool _setting_loop(movement_event_t event, void *context)
{
deadline_state_t *state = (deadline_state_t *) context;
watch_date_time_t date_time;
date_time = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], movement_get_current_timezone_offset());
if (event.event_type != EVENT_BACKGROUND_TASK)
_setting_display(event, state, date_time);
switch (event.event_type) {
case EVENT_TICK:
if (state->tick_freq == 8) {
if (HAL_GPIO_BTN_ALARM_read()) {
_increment_date(state, date_time);
_setting_display(event, state, date_time);
} else {
_change_tick_freq(4, state);
}
}
break;
case EVENT_ALARM_LONG_PRESS:
_change_tick_freq(8, state);
break;
case EVENT_ALARM_LONG_UP:
_change_tick_freq(4, state);
break;
case EVENT_LIGHT_LONG_PRESS:
_beep_button();
_reset_deadline(state);
break;
case EVENT_LIGHT_BUTTON_DOWN:
break;
case EVENT_LIGHT_BUTTON_UP:
state->current_page = (state->current_page + 1) % SETTINGS_NUM;
_setting_display(event, state, date_time);
break;
case EVENT_ALARM_BUTTON_UP:
_change_tick_freq(4, state);
_increment_date(state, date_time);
_setting_display(event, state, date_time);
break;
case EVENT_TIMEOUT:
_beep_button();
_background_alarm_schedule(settings, context);
_change_tick_freq(1, state);
state->mode = DEADLINE_FACE_RUNNING;
movement_move_to_face(0);
break;
case EVENT_MODE_BUTTON_UP:
_beep_disable();
_background_alarm_schedule(settings, context);
_running_init(state);
_running_display(event, state);
state->mode = DEADLINE_FACE_RUNNING;
break;
case EVENT_BACKGROUND_TASK:
_background_alarm_play(state);
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
/* Setup face */
void deadline_face_setup(uint8_t watch_face_index, void **context_ptr)
{
(void) watch_face_index;
if (*context_ptr != NULL)
return; /* Skip setup if context available */
/* Allocate state */
*context_ptr = malloc(sizeof(deadline_state_t));
memset(*context_ptr, 0, sizeof(deadline_state_t));
/* Store face index for background tasks */
deadline_state_t *state = (deadline_state_t *) *context_ptr;
state->face_idx = watch_face_index;
}
/* Activate face */
void deadline_face_activate(void *context)
{
deadline_state_t *state = (deadline_state_t *) context;
/* Set display options */
_running_init(state);
state->mode = DEADLINE_FACE_RUNNING;
state->current_index = _closest_deadline(state);
}
/* Loop face */
bool deadline_face_loop(movement_event_t event, void *context)
{
deadline_state_t *state = (deadline_state_t *) context;
switch (state->mode) {
case DEADLINE_FACE_SETTING:
_setting_loop(event, settings, context);
break;
default:
case DEADLINE_FACE_RUNNING:
_running_loop(event, settings, context);
break;
}
return true;
}
/* Resign face */
void deadline_face_resign(void *context)
{
(void) context;
}
/* Want background task */
movement_watch_face_advisory_t deadline_face_advise(void *context)
{
deadline_state_t *state = (deadline_state_t *) context;
if (!state->alarm_enabled)
return false;
/* Determine closest deadline */
watch_date_time_t now = watch_rtc_get_date_time();
uint32_t now_ts = watch_utility_date_time_to_unix_time(now, movement_get_current_timezone_offset());
uint32_t next_ts = state->deadlines[_closest_deadline(state)];
/* No active deadline */
if (next_ts < now_ts)
return false;
/* No deadline within next 60 seconds */
if (next_ts >= now_ts + 60)
return false;
/* Deadline within next minute. Let's set up an alarm */
watch_date_time_t next = watch_utility_date_time_from_unix_time(next_ts, movement_get_current_timezone_offset());
movement_request_wake();
movement_schedule_background_task_for_face(state->face_idx, next);
return false;
}

View File

@@ -0,0 +1,65 @@
/*
* MIT License
*
* Copyright (c) 2023-2024 Konrad Rieck
*
* 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 DEADLINE_FACE_H_
#define DEADLINE_FACE_H_
#include "movement.h"
/* Modes of face */
typedef enum {
DEADLINE_FACE_RUNNING = 0,
DEADLINE_FACE_SETTING
} deadline_mode_t;
/* Number of deadline dates */
#define DEADLINE_FACE_DATES (4)
/* Deadline configuration */
typedef struct {
deadline_mode_t mode:1;
uint8_t current_page:3;
uint8_t current_index:2;
uint8_t alarm_enabled:1;
uint8_t tick_freq;
uint8_t face_idx;
uint32_t deadlines[DEADLINE_FACE_DATES];
} deadline_state_t;
void deadline_face_setup(uint8_t watch_face_index, void **context_ptr);
void deadline_face_activate(void *context);
bool deadline_face_loop(movement_event_t event, void *context);
void deadline_face_resign(void *context);
movement_watch_face_advisory_t deadline_face_advise(void *context);
#define deadline_face ((const watch_face_t){ \
deadline_face_setup, \
deadline_face_activate, \
deadline_face_loop, \
deadline_face_resign, \
deadline_face_advise \
})
#endif // DEADLINE_FACE_H_

View File

@@ -0,0 +1,322 @@
#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() {
if (movement_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(uint8_t watch_face_index, void ** context_ptr) {
(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(void *context) {
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, void *context) {
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();
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();
}
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();
}
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(void *context) {
(void) context;
watch_clear_indicator(WATCH_INDICATOR_LAP);
}

View File

@@ -0,0 +1,94 @@
/*
* 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.
*/
#ifndef DISCGOLF_FACE_H_
#define DISCGOLF_FACE_H_
/*
* DISC GOLF face
*
* 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.
*/
#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(uint8_t watch_face_index, void ** context_ptr);
void discgolf_face_activate(void *context);
bool discgolf_face_loop(movement_event_t event, void *context);
void discgolf_face_resign(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_

View File

@@ -0,0 +1,331 @@
/*
* 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/fast_stopwatch_face.c \>
* from the Makefile.
*/
// FROM fast_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_t 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 fast_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(uint8_t watch_face_index, void ** context_ptr) {
(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(void *context) {
(void) context;
if (_is_running) {
movement_schedule_background_task(distant_future);
}
}
bool dual_timer_face_loop(movement_event_t event, 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);
}
return true;
}
void dual_timer_face_resign(void *context) {
(void) context;
movement_cancel_background_task();
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,108 @@
/*
* 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_
/*
* 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.
*
* 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/fast_stopwatch_face.c \>
* from the Makefile.
*/
#include "movement.h"
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(uint8_t watch_face_index, void ** context_ptr);
void dual_timer_face_activate(void *context);
bool dual_timer_face_loop(movement_event_t event, void *context);
void dual_timer_face_resign(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_

View File

@@ -0,0 +1,614 @@
/*
* MIT License
*
* Copyright (c) 2024 <David Volovskiy>
*
* 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 "endless_runner_face.h"
typedef enum {
JUMPING_FINAL_FRAME = 0,
NOT_JUMPING,
JUMPING_START,
} RunnerJumpState;
typedef enum {
SCREEN_TITLE = 0,
SCREEN_PLAYING,
SCREEN_LOSE,
SCREEN_TIME,
SCREEN_COUNT
} RunnerCurrScreen;
typedef enum {
DIFF_BABY = 0, // FREQ_SLOW FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames
DIFF_EASY, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames
DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames
DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames
DIFF_FUEL, // Mode where the top-right displays the amoount of fuel that you can be above the ground for, dodging obstacles. When on the ground, your fuel recharges.
DIFF_FUEL_1, // Same as DIFF_FUEL, but if your fuel is 0, then you won't recharge
DIFF_COUNT
} RunnerDifficulty;
#define NUM_GRID 12 // This the length that the obstacle track can be on
#define FREQ 8 // Frequency request for the game
#define FREQ_SLOW 4 // Frequency request for baby mode
#define JUMP_FRAMES 2 // Wait this many frames on difficulties above EASY before coming down from the jump button pressed
#define JUMP_FRAMES_EASY 3 // Wait this many frames on difficulties at or below EASY before coming down from the jump button pressed
#define MIN_ZEROES 4 // At minimum, we'll have this many spaces between obstacles
#define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode
#define MAX_HI_SCORE 9999 // Max hi score to store and display on the title screen.
#define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39
#define JUMP_FRAMES_FUEL 30 // The max fuel that fuel that the fuel mode game will hold
#define JUMP_FRAMES_FUEL_RECHARGE 3 // How much fuel each frame on the ground adds
#define MAX_DISP_SCORE_FUEL 9 // Since the fuel mode displays the score in the weekday slot, two digits will display wonky data
typedef struct {
uint32_t obst_pattern;
uint16_t obst_indx : 8;
uint16_t jump_state : 5;
uint16_t sec_before_moves : 3;
uint16_t curr_score : 10;
uint16_t curr_screen : 4;
bool loc_2_on;
bool loc_3_on;
bool success_jump;
bool fuel_mode;
uint8_t fuel;
} game_state_t;
static game_state_t game_state;
static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8;
static void print_binary(uint32_t value, int bits) {
#if __EMSCRIPTEN__
for (int i = bits - 1; i >= 0; i--) {
// Print each bit
printf("%lu", (value >> i) & 1);
// Optional: add a space every 4 bits for readability
if (i % 4 == 0 && i != 0) {
printf(" ");
}
}
printf("\r\n");
#else
(void) value;
(void) bits;
#endif
return;
}
static uint32_t get_random(uint32_t max) {
#if __EMSCRIPTEN__
return rand() % max;
#else
return arc4random_uniform(max);
#endif
}
static uint32_t get_random_nonzero(uint32_t max) {
uint32_t random;
do
{
random = get_random(max);
} while (random == 0);
return random;
}
static uint32_t get_random_kinda_nonzero(uint32_t max) {
// Returns a number that's between 1 and max, unless max is 0 or 1, then it returns 0 to max.
if (max == 0) return 0;
else if (max == 1) return get_random(max);
return get_random_nonzero(max);
}
static uint32_t get_random_fuel(uint32_t prev_val) {
static uint8_t prev_rand_subset = 0;
uint32_t rand;
uint8_t max_ones, subset;
uint32_t rand_legal = 0;
prev_val = prev_val & ~0xFFFF;
for (int i = 0; i < 2; i++) {
subset = 0;
max_ones = 8;
if (prev_rand_subset > 4)
max_ones -= prev_rand_subset;
rand = get_random_kinda_nonzero(max_ones);
if (rand > 5 && prev_rand_subset) rand = 5; // The gap of one or two is awkward
for (uint32_t j = 0; j < rand; j++) {
subset |= (1 << j);
}
if (prev_rand_subset >= 7)
subset = subset << 1;
subset &= 0xFF;
rand_legal |= subset << (8 * i);
prev_rand_subset = rand;
}
rand_legal = prev_val | rand_legal;
print_binary(rand_legal, 32);
return rand_legal;
}
static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) {
/** @brief A legal random number starts with the previous number (which should be the 12 bits on the screen).
* @param prev_val The previous value to tack onto. The return will have its first NUM_GRID MSBs be the same as prev_val, and the rest be new
* @param difficulty To dictate how spread apart the obsticles must be
* @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val
*/
uint8_t min_zeros = (difficulty == DIFF_HARD) ? MIN_ZEROES_HARD : MIN_ZEROES;
uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1;
uint32_t rand = get_random_nonzero(max);
uint32_t rand_legal = 0;
prev_val = prev_val & ~max;
for (int i = (NUM_GRID + 1); i <= _num_bits_obst_pattern; i++) {
uint32_t mask = 1 << (_num_bits_obst_pattern - i);
bool msb = (rand & mask) >> (_num_bits_obst_pattern - i);
if (msb) {
rand_legal = rand_legal << min_zeros;
i+=min_zeros;
}
rand_legal |= msb;
rand_legal = rand_legal << 1;
}
rand_legal = rand_legal & max;
for (int i = 0; i <= min_zeros; i++) {
if (prev_val & (1 << (i + _num_bits_obst_pattern - NUM_GRID))){
rand_legal = rand_legal >> (min_zeros - i);
break;
}
}
rand_legal = prev_val | rand_legal;
print_binary(rand_legal, 32);
return rand_legal;
}
static void display_ball(bool jumping) {
if (!jumping) {
watch_set_pixel(0, 21);
watch_set_pixel(1, 21);
watch_set_pixel(0, 20);
watch_set_pixel(1, 20);
watch_clear_pixel(1, 17);
watch_clear_pixel(2, 20);
watch_clear_pixel(2, 21);
}
else {
watch_clear_pixel(0, 21);
watch_clear_pixel(1, 21);
watch_clear_pixel(0, 20);
watch_set_pixel(1, 20);
watch_set_pixel(1, 17);
watch_set_pixel(2, 20);
watch_set_pixel(2, 21);
}
}
static void display_score(uint8_t score) {
char buf[3];
if (game_state.fuel_mode) {
score %= (MAX_DISP_SCORE_FUEL + 1);
sprintf(buf, "%1d", score);
watch_display_string(buf, 0);
}
else {
score %= (MAX_DISP_SCORE + 1);
sprintf(buf, "%2d", score);
watch_display_string(buf, 2);
}
}
static void add_to_score(endless_runner_state_t *state) {
if (game_state.curr_score <= MAX_HI_SCORE) {
game_state.curr_score++;
if (game_state.curr_score > state -> hi_score)
state -> hi_score = game_state.curr_score;
}
game_state.success_jump = true;
display_score(game_state.curr_score);
}
static void display_fuel(uint8_t subsecond, uint8_t difficulty) {
char buf[4];
if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % (FREQ/2) == 0) {
watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled.
return;
}
sprintf(buf, "%2d", game_state.fuel);
watch_display_string(buf, 2);
}
static void check_and_reset_hi_score(endless_runner_state_t *state) {
// Resets the hi score at the beginning of each month.
watch_date_time_t date_time = watch_rtc_get_date_time();
if ((state -> year_last_hi_score != date_time.unit.year) ||
(state -> month_last_hi_score != date_time.unit.month))
{
// The high score resets itself every new month.
state -> hi_score = 0;
state -> year_last_hi_score = date_time.unit.year;
state -> month_last_hi_score = date_time.unit.month;
}
}
static void display_difficulty(uint16_t difficulty) {
switch (difficulty)
{
case DIFF_BABY:
watch_display_string(" b", 2);
break;
case DIFF_EASY:
watch_display_string(" E", 2);
break;
case DIFF_HARD:
watch_display_string(" H", 2);
break;
case DIFF_FUEL:
watch_display_string(" F", 2);
break;
case DIFF_FUEL_1:
watch_display_string("1F", 2);
break;
case DIFF_NORM:
default:
watch_display_string(" N", 2);
break;
}
game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1;
}
static void change_difficulty(endless_runner_state_t *state) {
state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT;
display_difficulty(state -> difficulty);
if (state -> soundOn) {
if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30);
else watch_buzzer_play_note(BUZZER_NOTE_C5, 30);
}
}
static void toggle_sound(endless_runner_state_t *state) {
state -> soundOn = !state -> soundOn;
if (state -> soundOn){
watch_buzzer_play_note(BUZZER_NOTE_C5, 30);
watch_set_indicator(WATCH_INDICATOR_BELL);
}
else {
watch_clear_indicator(WATCH_INDICATOR_BELL);
}
}
static void display_title(endless_runner_state_t *state) {
uint16_t hi_score = state -> hi_score;
uint8_t difficulty = state -> difficulty;
bool sound_on = state -> soundOn;
game_state.curr_screen = SCREEN_TITLE;
memset(&game_state, 0, sizeof(game_state));
game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay.
if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second
watch_set_colon();
if (hi_score > MAX_HI_SCORE) {
watch_display_string("ER HS --", 0);
}
else {
char buf[14];
sprintf(buf, "ER HS%4d", hi_score);
watch_display_string(buf, 0);
}
display_difficulty(difficulty);
}
static void display_time(watch_date_time_t date_time, bool clock_mode_24h) {
static watch_date_time_t previous_date_time;
char buf[6 + 1];
// If the hour needs updating or it's the first time displaying the time
if ((game_state.curr_screen != SCREEN_TIME) || (date_time.unit.hour != previous_date_time.unit.hour)) {
uint8_t hour = date_time.unit.hour;
game_state.curr_screen = SCREEN_TIME;
if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
else {
if (hour >= 12) watch_set_indicator(WATCH_INDICATOR_PM);
hour %= 12;
if (hour == 0) hour = 12;
}
watch_set_colon();
sprintf( buf, "%2d%02d ", hour, date_time.unit.minute);
watch_display_string(buf, 4);
}
// If both digits of the minute need updating
else if ((date_time.unit.minute / 10) != (previous_date_time.unit.minute / 10)) {
sprintf( buf, "%02d ", date_time.unit.minute);
watch_display_string(buf, 6);
}
// If only the ones-place of the minute needs updating.
else if (date_time.unit.minute != previous_date_time.unit.minute) {
sprintf( buf, "%d ", date_time.unit.minute % 10);
watch_display_string(buf, 7);
}
previous_date_time.reg = date_time.reg;
}
static void begin_playing(endless_runner_state_t *state) {
uint8_t difficulty = state -> difficulty;
game_state.curr_screen = SCREEN_PLAYING;
watch_clear_colon();
movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ);
if (game_state.fuel_mode) {
watch_display_string(" ", 0);
game_state.obst_pattern = get_random_fuel(0);
if ((16 * JUMP_FRAMES_FUEL_RECHARGE) < JUMP_FRAMES_FUEL) // 16 frames of zeros at the start of a level
game_state.fuel = JUMP_FRAMES_FUEL - (16 * JUMP_FRAMES_FUEL_RECHARGE); // Have it below its max to show it counting up when starting.
if (game_state.fuel < JUMP_FRAMES_FUEL_RECHARGE) game_state.fuel = JUMP_FRAMES_FUEL_RECHARGE;
}
else {
watch_display_string(" ", 2);
game_state.obst_pattern = get_random_legal(0, difficulty);
}
game_state.jump_state = NOT_JUMPING;
display_ball(game_state.jump_state != NOT_JUMPING);
display_score( game_state.curr_score);
if (state -> soundOn){
watch_buzzer_play_note(BUZZER_NOTE_C5, 200);
watch_buzzer_play_note(BUZZER_NOTE_E5, 200);
watch_buzzer_play_note(BUZZER_NOTE_G5, 200);
}
}
static void display_lose_screen(endless_runner_state_t *state) {
game_state.curr_screen = SCREEN_LOSE;
game_state.curr_score = 0;
watch_display_string(" LOSE ", 0);
if (state -> soundOn)
watch_buzzer_play_note(BUZZER_NOTE_A1, 600);
else
delay_ms(600);
}
static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) {
static bool prev_obst_pos_two = 0;
switch (grid_loc)
{
case 2:
game_state.loc_2_on = obstacle;
if (obstacle)
watch_set_pixel(0, 20);
else if (game_state.jump_state != NOT_JUMPING) {
watch_clear_pixel(0, 20);
if (game_state.fuel_mode && prev_obst_pos_two)
add_to_score(state);
}
prev_obst_pos_two = obstacle;
break;
case 3:
game_state.loc_3_on = obstacle;
if (obstacle)
watch_set_pixel(1, 21);
else if (game_state.jump_state != NOT_JUMPING)
watch_clear_pixel(1, 21);
break;
case 1:
if (!game_state.fuel_mode && obstacle) // If an obstacle is here, it means the ball cleared it
add_to_score(state);
//fall through
case 0:
case 5:
if (obstacle)
watch_set_pixel(0, 18 + grid_loc);
else
watch_clear_pixel(0, 18 + grid_loc);
break;
case 4:
if (obstacle)
watch_set_pixel(1, 22);
else
watch_clear_pixel(1, 22);
break;
case 6:
if (obstacle)
watch_set_pixel(1, 0);
else
watch_clear_pixel(1, 0);
break;
case 7:
case 8:
if (obstacle)
watch_set_pixel(0, grid_loc - 6);
else
watch_clear_pixel(0, grid_loc - 6);
break;
case 9:
case 10:
if (obstacle)
watch_set_pixel(0, grid_loc - 5);
else
watch_clear_pixel(0, grid_loc - 5);
break;
case 11:
if (obstacle)
watch_set_pixel(1, 6);
else
watch_clear_pixel(1, 6);
break;
default:
break;
}
}
static void stop_jumping(endless_runner_state_t *state) {
game_state.jump_state = NOT_JUMPING;
display_ball(game_state.jump_state != NOT_JUMPING);
if (state -> soundOn){
if (game_state.success_jump)
watch_buzzer_play_note(BUZZER_NOTE_C5, 60);
else
watch_buzzer_play_note(BUZZER_NOTE_C3, 60);
}
game_state.success_jump = false;
}
static void display_obstacles(endless_runner_state_t *state) {
for (int i = 0; i < NUM_GRID; i++) {
// Use a bitmask to isolate each bit and shift it to the least significant position
uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i);
bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i);
display_obstacle(obstacle, i, state);
}
game_state.obst_pattern = game_state.obst_pattern << 1;
game_state.obst_indx++;
if (game_state.fuel_mode) {
if (game_state.obst_indx >= (_num_bits_obst_pattern / 2)) {
game_state.obst_indx = 0;
game_state.obst_pattern = get_random_fuel(game_state.obst_pattern);
}
}
else if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) {
game_state.obst_indx = 0;
game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty);
}
}
static void update_game(endless_runner_state_t *state, uint8_t subsecond) {
uint8_t curr_jump_frame = 0;
if (game_state.sec_before_moves != 0) {
if (subsecond == 0) --game_state.sec_before_moves;
return;
}
display_obstacles(state);
switch (game_state.jump_state)
{
case NOT_JUMPING:
if (game_state.fuel_mode) {
for (int i = 0; i < JUMP_FRAMES_FUEL_RECHARGE; i++)
{
if(game_state.fuel >= JUMP_FRAMES_FUEL || (state -> difficulty == DIFF_FUEL_1 && !game_state.fuel))
break;
game_state.fuel++;
}
}
break;
case JUMPING_FINAL_FRAME:
stop_jumping(state);
break;
default:
if (game_state.fuel_mode) {
if (!game_state.fuel)
game_state.jump_state = JUMPING_FINAL_FRAME;
else
game_state.fuel--;
if (!HAL_GPIO_BTN_ALARM_read() && !HAL_GPIO_BTN_LIGHT_read()) stop_jumping(state);
}
else {
curr_jump_frame = game_state.jump_state - NOT_JUMPING;
if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES))
game_state.jump_state = JUMPING_FINAL_FRAME;
else
game_state.jump_state++;
}
break;
}
if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) {
delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen.
display_lose_screen(state);
}
else if (game_state.fuel_mode)
display_fuel(subsecond, state -> difficulty);
}
void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(endless_runner_state_t));
memset(*context_ptr, 0, sizeof(endless_runner_state_t));
endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr;
state->difficulty = DIFF_NORM;
}
}
void endless_runner_face_activate(void *context) {
(void) context;
}
bool endless_runner_face_loop(movement_event_t event, void *context) {
endless_runner_state_t *state = (endless_runner_state_t *)context;
switch (event.event_type) {
case EVENT_ACTIVATE:
check_and_reset_hi_score(state);
if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL);
display_title(state);
break;
case EVENT_TICK:
switch (game_state.curr_screen)
{
case SCREEN_TITLE:
case SCREEN_LOSE:
break;
default:
update_game(state, event.subsecond);
break;
}
break;
case EVENT_LIGHT_BUTTON_UP:
case EVENT_ALARM_BUTTON_UP:
if (game_state.curr_screen == SCREEN_TITLE)
begin_playing(state);
else if (game_state.curr_screen == SCREEN_LOSE)
display_title(state);
break;
case EVENT_LIGHT_LONG_PRESS:
if (game_state.curr_screen == SCREEN_TITLE)
change_difficulty(state);
break;
case EVENT_LIGHT_BUTTON_DOWN:
case EVENT_ALARM_BUTTON_DOWN:
if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){
if (game_state.fuel_mode && !game_state.fuel) break;
game_state.jump_state = JUMPING_START;
display_ball(game_state.jump_state != NOT_JUMPING);
}
break;
case EVENT_ALARM_LONG_PRESS:
if (game_state.curr_screen != SCREEN_PLAYING)
toggle_sound(state);
break;
case EVENT_TIMEOUT:
if (game_state.curr_screen != SCREEN_TITLE)
display_title(state);
break;
case EVENT_LOW_ENERGY_UPDATE:
display_time(watch_rtc_get_date_time(), movement_clock_mode_24h());
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void endless_runner_face_resign(void *context) {
(void) context;
}

View File

@@ -0,0 +1,62 @@
/*
* MIT License
*
* Copyright (c) 2024 <David Volovskiy>
*
* 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 ENDLESS_RUNNER_FACE_H_
#define ENDLESS_RUNNER_FACE_H_
#include "movement.h"
/*
ENDLESS_RUNNER face
This is a basic endless-runner, like the [Chrome Dino game](https://en.wikipedia.org/wiki/Dinosaur_Game).
On the title screen, you can select a difficulty by long-pressing LIGHT or toggle sound by long-pressing ALARM.
LED or ALARM are used to jump.
High-score is displayed on the top-right on the title screen. During a game, the current score is displayed.
*/
typedef struct {
uint16_t hi_score : 10;
uint8_t difficulty : 3;
uint8_t month_last_hi_score : 4;
uint8_t year_last_hi_score : 6;
uint8_t soundOn : 1;
/* 24 bits, likely aligned to 32 bits = 4 bytes */
} endless_runner_state_t;
void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr);
void endless_runner_face_activate(void *context);
bool endless_runner_face_loop(movement_event_t event, void *context);
void endless_runner_face_resign(void *context);
#define endless_runner_face ((const watch_face_t){ \
endless_runner_face_setup, \
endless_runner_face_activate, \
endless_runner_face_loop, \
endless_runner_face_resign, \
NULL, \
})
#endif // ENDLESS_RUNNER_FACE_H_

View File

@@ -0,0 +1,73 @@
/*
* MIT License
*
* Copyright (c) 2023 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 "flashlight_face.h"
void flashlight_face_setup(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(flashlight_state_t));
memset(*context_ptr, 0, sizeof(flashlight_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 flashlight_face_activate(void *context) {
(void) context;
HAL_GPIO_A2_out();
HAL_GPIO_A2_clr();
}
bool flashlight_face_loop(movement_event_t event, void *context) {
(void) context;
switch (event.event_type) {
case EVENT_ACTIVATE:
watch_display_string("FL", 0);
break;
case EVENT_LIGHT_BUTTON_DOWN:
break;
case EVENT_LIGHT_BUTTON_UP:
HAL_GPIO_A2_toggle();
break;
case EVENT_TIMEOUT:
movement_move_to_face(0);
break;
default:
return movement_default_loop_handler(event);
}
return true;
}
void flashlight_face_resign(void *context) {
(void) context;
HAL_GPIO_A2_clr();
HAL_GPIO_A2_off();
}

View File

@@ -0,0 +1,59 @@
/*
* MIT License
*
* Copyright (c) 2023 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 FLASHLIGHT_FACE_H_
#define FLASHLIGHT_FACE_H_
/*
* FLASHLIGHT face
*
* A flashlight for use with the Flashlight sensor board.
*
* When the watch face appears, the display will show "FL" in the top two positions.
* Pressing the Light button will toggle the flashlight on and off.
*
*/
#include "movement.h"
typedef struct {
// Anything you need to keep track of, put it here!
uint8_t unused;
} flashlight_state_t;
void flashlight_face_setup(uint8_t watch_face_index, void ** context_ptr);
void flashlight_face_activate(void *context);
bool flashlight_face_loop(movement_event_t event, void *context);
void flashlight_face_resign(void *context);
#define flashlight_face ((const watch_face_t){ \
flashlight_face_setup, \
flashlight_face_activate, \
flashlight_face_loop, \
flashlight_face_resign, \
NULL, \
})
#endif // FLASHLIGHT_FACE_H_

View File

@@ -0,0 +1,359 @@
/*
* 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(geomancy_state_t *state);
static nibble_t _geomancy_pick_figure(void);
static tribble_t _iching_pick_trigram(void);
static uint8_t _iching_form_hexagram(void);
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(uint8_t watch_face_index, void ** context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(geomancy_state_t));
memset(*context_ptr, 0, sizeof(geomancy_state_t));
}
}
void geomancy_face_activate(void *context) {
(void) context;
}
bool geomancy_face_loop(movement_event_t event, 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);
}
return true;
}
void geomancy_face_resign(void *context) {
(void) context;
}
// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
/** @brief display handler */
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);
}

View File

@@ -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_
/*
* 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)
*
*/
#include "movement.h"
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(uint8_t watch_face_index, void ** context_ptr);
void geomancy_face_activate(void *context);
bool geomancy_face_loop(movement_event_t event, void *context);
void geomancy_face_resign(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_

View File

@@ -0,0 +1,154 @@
/*
* MIT License
*
* Copyright (c) 2023 tslil clingman
*
* 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 "habit_face.h"
#include "watch_private_display.h"
#include "watch_rtc.h"
#include "watch_slcd.h"
#include "watch_utility.h"
#include <stdlib.h>
#include <string.h>
static inline uint32_t today_unix(const uint32_t utc_offset) {
const watch_date_time_t dt = watch_rtc_get_date_time();
return watch_utility_convert_to_unix_time(dt.unit.year + 2020, dt.unit.month,
dt.unit.day, 0, 0, 0, utc_offset);
}
static inline uint32_t days_since_unix(const uint32_t since,
const uint32_t until) {
return (until - since) / (60 * 60 * 24);
}
typedef struct {
uint16_t total_count;
uint8_t lookback;
uint32_t last_update;
bool display_total;
} habit_state_t;
void habit_face_setup(uint8_t watch_face_index,
void **context_ptr) {
(void)watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(habit_state_t));
memset(*context_ptr, 0, sizeof(habit_state_t));
habit_state_t *state = (habit_state_t *)*context_ptr;
state->lookback = 0;
state->last_update = watch_utility_offset_timestamp(
today_unix(movement_get_current_timezone_offset()), -24, 0, 0);
}
}
static inline void display_state(habit_state_t *state) {
const bool can_do = (state->lookback & 1) == 0;
char buf[16];
if (can_do) {
watch_clear_indicator(WATCH_INDICATOR_LAP);
} else {
watch_set_indicator(WATCH_INDICATOR_LAP);
}
if (state->display_total) {
sprintf(buf, "HA %03dtot", state->total_count);
watch_display_string(buf, 0);
} else {
sprintf(buf, "HA d%c", can_do ? 'o' : 'n');
uint8_t copy = state->lookback;
for (uint8_t c = 0; copy; copy >>= 2, c++) {
switch (copy & 3) {
case 0:
break;
case 1:
buf[4 + c] = 'I';
break;
case 2:
buf[4 + c] = '1';
break;
case 3:
buf[4 + c] = '|';
break;
}
}
watch_display_string(buf, 0);
if (can_do) {
const uint64_t segmap = Segment_Map[4] >> 48;
watch_set_pixel((segmap & 0xFF) >> 6, segmap & 0x3F);
}
}
}
void habit_face_activate(void *context) {
habit_state_t *state = (habit_state_t *)context;
display_state(state);
}
bool habit_face_loop(movement_event_t event,
void *context) {
habit_state_t *state = (habit_state_t *)context;
const uint32_t today_now_unix = today_unix(movement_get_current_timezone_offset());
const bool can_do = (state->lookback & 1) == 0;
switch (event.event_type) {
case EVENT_ACTIVATE:
case EVENT_TICK: {
display_state(state);
if (today_now_unix > state->last_update) {
uint8_t num_shifts = days_since_unix(state->last_update, today_now_unix);
if (num_shifts > 7)
num_shifts = 7;
state->lookback <<= num_shifts;
state->last_update = today_now_unix;
}
break;
}
case EVENT_LIGHT_BUTTON_UP: {
state->display_total = !state->display_total;
display_state(state);
break;
}
case EVENT_ALARM_BUTTON_UP: {
if (can_do) {
state->lookback |= 1;
state->total_count++;
state->last_update = today_now_unix;
display_state(state);
};
break;
}
case EVENT_TIMEOUT: {
movement_move_to_face(0);
break;
}
default:
return movement_default_loop_handler(event);
}
return true;
}
void habit_face_resign(void *context) {
(void)context;
}

View File

@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2023 tslil clingman
*
* 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 HABIT_FACE_H_
#define HABIT_FACE_H_
/*
* Habit tracking face
*
* Allows the user to record a single succesful instance of a particular habit
* occuring per day, and displays history for eight days prior as well as a
* total counter.
*
*/
#include "movement.h"
void habit_face_setup(uint8_t watch_face_index,
void **context_ptr);
void habit_face_activate(void *context);
bool habit_face_loop(movement_event_t event, void *context);
void habit_face_resign(void *context);
#define habit_face ((const watch_face_t){ \
habit_face_setup, \
habit_face_activate, \
habit_face_loop, \
habit_face_resign, \
NULL, \
})
#endif // HABIT_FACE_H_

View File

@@ -0,0 +1,393 @@
/*
* MIT License
*
* Copyright (c) 2023 Chris Ellis
*
* 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 "higher_lower_game_face.h"
#include "watch_private_display.h"
#define TITLE_TEXT "Hi-Lo"
#define GAME_BOARD_SIZE 6
#define MAX_BOARDS 40
#define GUESSES_PER_SCREEN 5
#define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN)
#define STATUS_DISPLAY_START 0
#define BOARD_SCORE_DISPLAY_START 2
#define BOARD_DISPLAY_START 4
#define BOARD_DISPLAY_END 9
#define MIN_CARD_VALUE 2
#define MAX_CARD_VALUE 14
#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)
#define CARD_SUIT_COUNT 4
#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT)
#define FLIP_BOARD_DIRECTION false
typedef struct card_t {
uint8_t value;
bool revealed;
} card_t;
typedef enum {
A, B, C, D, E, F, G
} segment_t;
typedef enum {
HL_GUESS_EQUAL,
HL_GUESS_HIGHER,
HL_GUESS_LOWER
} guess_t;
typedef enum {
HL_GS_TITLE_SCREEN,
HL_GS_GUESSING,
HL_GS_WIN,
HL_GS_LOSE,
HL_GS_SHOW_SCORE,
} game_state_t;
static game_state_t game_state = HL_GS_TITLE_SCREEN;
static card_t game_board[GAME_BOARD_SIZE] = {0};
static uint8_t guess_position = 0;
static uint8_t score = 0;
static uint8_t completed_board_count = 0;
static uint8_t deck[DECK_SIZE] = {0};
static uint8_t current_card = 0;
static uint8_t generate_random_number(uint8_t num_values) {
// Emulator: use rand. Hardware: use arc4random.
#if __EMSCRIPTEN__
return rand() % num_values;
#else
return arc4random_uniform(num_values);
#endif
}
static void stack_deck(void) {
for (size_t i = 0; i < CARD_RANK_COUNT; i++) {
for (size_t j = 0; j < CARD_SUIT_COUNT; j++)
deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i;
}
}
static void shuffle_deck(void) {
// Randomize shuffle with Fisher Yates
size_t i;
size_t j;
uint8_t tmp;
for (i = DECK_SIZE - 1; i > 0; i--) {
j = generate_random_number(0xFF) % (i + 1);
tmp = deck[j];
deck[j] = deck[i];
deck[i] = tmp;
}
}
static void reset_deck(void) {
current_card = 0;
stack_deck();
shuffle_deck();
}
static uint8_t get_next_card(void) {
if (current_card >= DECK_SIZE)
reset_deck();
return deck[current_card++];
}
static void reset_board(bool first_round) {
// First card is random on the first board, and carried over from the last position on subsequent boards
const uint8_t first_card_value = first_round
? get_next_card()
: game_board[GAME_BOARD_SIZE - 1].value;
game_board[0].value = first_card_value;
game_board[0].revealed = true; // Always reveal first card
// Fill remainder of board
for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) {
game_board[i] = (card_t) {
.value = get_next_card(),
.revealed = false
};
}
}
static void init_game(void) {
watch_clear_display();
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
watch_display_string("GA", STATUS_DISPLAY_START);
reset_deck();
reset_board(true);
score = 0;
completed_board_count = 0;
guess_position = 1;
}
static void set_segment_at_position(segment_t segment, uint8_t position) {
const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF;
const uint8_t com_pin = position_segment_data >> 6;
const uint8_t seg = position_segment_data & 0x3F;
watch_set_pixel(com_pin, seg);
}
static void render_board_position(size_t board_position) {
const size_t display_position = FLIP_BOARD_DIRECTION
? BOARD_DISPLAY_START + board_position
: BOARD_DISPLAY_END - board_position;
const bool revealed = game_board[board_position].revealed;
//// Current position indicator spot
//if (board_position == guess_position) {
// watch_display_character('-', display_position);
// return;
//}
if (!revealed) {
// Higher or lower indicator (currently just an empty space)
watch_display_character(' ', display_position);
//set_segment_at_position(F, display_position);
return;
}
const uint8_t value = game_board[board_position].value;
switch (value) {
case 14: // A (≡)
watch_display_character(' ', display_position);
set_segment_at_position(A, display_position);
set_segment_at_position(D, display_position);
set_segment_at_position(G, display_position);
break;
case 13: // K (=)
watch_display_character(' ', display_position);
set_segment_at_position(A, display_position);
set_segment_at_position(D, display_position);
break;
case 12: // Q (-)
watch_display_character('-', display_position);
break;
default: {
const char display_char = (value - MIN_CARD_VALUE) + '0';
watch_display_character(display_char, display_position);
}
}
}
static void render_board(void) {
for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) {
render_board_position(i);
}
}
static void render_board_count(void) {
// Render completed boards (screens)
char buf[3] = {0};
snprintf(buf, sizeof(buf), "%2hhu", completed_board_count);
watch_display_string(buf, BOARD_SCORE_DISPLAY_START);
}
static void render_final_score(void) {
watch_display_string("SC", STATUS_DISPLAY_START);
char buf[7] = {0};
const uint8_t complete_boards = score / GUESSES_PER_SCREEN;
snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score);
watch_set_colon();
watch_display_string(buf, BOARD_DISPLAY_START);
}
static guess_t get_answer(void) {
if (guess_position < 1 || guess_position > GAME_BOARD_SIZE)
return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this.
game_board[guess_position].revealed = true;
const uint8_t previous_value = game_board[guess_position - 1].value;
const uint8_t current_value = game_board[guess_position].value;
if (current_value > previous_value)
return HL_GUESS_HIGHER;
else if (current_value < previous_value)
return HL_GUESS_LOWER;
else
return HL_GUESS_EQUAL;
}
static void do_game_loop(guess_t user_guess) {
switch (game_state) {
case HL_GS_TITLE_SCREEN:
init_game();
render_board();
render_board_count();
game_state = HL_GS_GUESSING;
break;
case HL_GS_GUESSING: {
const guess_t answer = get_answer();
// Render answer indicator
switch (answer) {
case HL_GUESS_EQUAL:
watch_display_string("==", STATUS_DISPLAY_START);
break;
case HL_GUESS_HIGHER:
watch_display_string("HI", STATUS_DISPLAY_START);
break;
case HL_GUESS_LOWER:
watch_display_string("LO", STATUS_DISPLAY_START);
break;
}
// Scoring
if (answer == user_guess) {
score++;
} else if (answer == HL_GUESS_EQUAL) {
// No score for two consecutive identical cards
} else {
// Incorrect guess, game over
watch_display_string("GO", STATUS_DISPLAY_START);
game_board[guess_position].revealed = true;
render_board_position(guess_position);
game_state = HL_GS_LOSE;
return;
}
if (score >= WIN_SCORE) {
// Win, perhaps some kind of animation sequence?
watch_display_string("WI", STATUS_DISPLAY_START);
watch_display_string(" ", BOARD_SCORE_DISPLAY_START);
watch_display_string("------", BOARD_DISPLAY_START);
game_state = HL_GS_WIN;
return;
}
// Next guess position
const bool final_board_guess = guess_position == GAME_BOARD_SIZE - 1;
if (final_board_guess) {
// Seed new board
completed_board_count++;
render_board_count();
guess_position = 1;
reset_board(false);
render_board();
} else {
guess_position++;
render_board_position(guess_position - 1);
render_board_position(guess_position);
}
break;
}
case HL_GS_WIN:
case HL_GS_LOSE:
// Show score screen on button press from either state
watch_clear_display();
render_final_score();
game_state = HL_GS_SHOW_SCORE;
break;
case HL_GS_SHOW_SCORE:
watch_clear_display();
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
watch_display_string("GA", STATUS_DISPLAY_START);
game_state = HL_GS_TITLE_SCREEN;
break;
default:
watch_display_string("ERROR", BOARD_DISPLAY_START);
break;
}
}
static void light_button_handler(void) {
do_game_loop(HL_GUESS_HIGHER);
}
static void alarm_button_handler(void) {
do_game_loop(HL_GUESS_LOWER);
}
void higher_lower_game_face_setup(uint8_t watch_face_index, void **context_ptr) {
(void) watch_face_index;
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(higher_lower_game_face_state_t));
memset(*context_ptr, 0, sizeof(higher_lower_game_face_state_t));
// Do any one-time tasks in here; the inside of this conditional happens only at boot.
memset(game_board, 0, sizeof(game_board));
}
// Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
}
void higher_lower_game_face_activate(void *context) {
higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;
(void) state;
// Handle any tasks related to your watch face coming on screen.
game_state = HL_GS_TITLE_SCREEN;
}
bool higher_lower_game_face_loop(movement_event_t event, void *context) {
higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context;
(void) state;
switch (event.event_type) {
case EVENT_ACTIVATE:
// Show your initial UI here.
watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START);
watch_display_string("GA", STATUS_DISPLAY_START);
break;
case EVENT_TICK:
// If needed, update your display here.
break;
case EVENT_LIGHT_BUTTON_UP:
light_button_handler();
break;
case EVENT_LIGHT_BUTTON_DOWN:
// Don't trigger light
break;
case EVENT_ALARM_BUTTON_UP:
alarm_button_handler();
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;
default:
return movement_default_loop_handler(event);
}
// 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 higher_lower_game_face_resign(void *context) {
(void) context;
// handle any cleanup before your watch face goes off-screen.
}

View File

@@ -0,0 +1,106 @@
/*
* MIT License
*
* Copyright (c) 2023 Chris Ellis
*
* 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 HIGHER_LOWER_GAME_FACE_H_
#define HIGHER_LOWER_GAME_FACE_H_
#include "movement.h"
/*
* Higher-Lower game face
* ======================
*
* A game face based on the "higher-lower" card game where the objective is to correctly guess if the next card will
* be higher or lower than the last revealed cards.
*
* Game Flow:
* - When the face is selected, the "Hi-Lo" "Title" screen will be displayed, and the status indicator will display "GA" for game
* - Pressing `ALARM` or `LIGHT` will start the game and proceed to the "Guessing" screen
* - The first card will be revealed and the player must now make a guess
* - A player can guess `Higher` by pressing the `LIGHT` button, and `Lower` by pressing the `ALARM` button
* - The status indicator will show the result of the guess: HI (Higher), LO (Lower), or == (Equal)
* - There are five guesses to make on each game screen, once the end of the screen is reached, a new screen
* will be started, with the last revealed card carried over
* - The number of completed screens is displayed in the top right (see Scoring)
* - If the player has guessed correctly, the score is updated and play continues (see Scoring)
* - If the player has guessed incorrectly, the status will change to GO (Game Over)
* - The current card will be revealed
* - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen
* - If the game is won, the status indicator will display "WI" and the "Win" screen will be displayed
* - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen
* - The status indicator will change to "SC" when the final score is displayed
* - The number of completed game screens will be displayed on using the first two digits
* - The number of correct guesses will be displayed using the final three digits
* - E.g. "13: 063" represents 13 completed screens, with 63 correct guesses
* - Pressing `ALARM` or `LIGHT` while on the "Score" screen will transition to back to the "Title" screen
*
* Scoring:
* - If the player guesses correctly (HI/LO) a point is gained
* - If the player guesses incorrectly the game ends
* - Unless the revealed card is equal (==) to the last card, in which case play continues, but no point is gained
* - If the player completes 40 screens full of cards, the game ends and a win screen is displayed
*
* Misc:
* The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at
* the following mapping of card values to screen display, but am open to better suggestions:
*
* Thanks to voloved for adding deck shuffling and drawing!
*
* | Cards | |
* |---------|--------------------------|
* | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|
* | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡|
*
* A previous alternative can be found in the git history:
* | Cards | |
* |---------|--------------------------|
* | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A|
* | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H|
*
*
* Future Ideas:
* - Add sounds
* - Save/Display high score
* - Add a "Win" animation
* - Consider using lap indicator for larger score limit
*/
typedef struct {
// Anything you need to keep track of, put it here!
} higher_lower_game_face_state_t;
void higher_lower_game_face_setup(uint8_t watch_face_index, void ** context_ptr);
void higher_lower_game_face_activate(void *context);
bool higher_lower_game_face_loop(movement_event_t event, void *context);
void higher_lower_game_face_resign(void *context);
#define higher_lower_game_face ((const watch_face_t){ \
higher_lower_game_face_setup, \
higher_lower_game_face_activate, \
higher_lower_game_face_loop, \
higher_lower_game_face_resign, \
NULL, \
})
#endif // HIGHER_LOWER_GAME_FACE_H_

View File

@@ -0,0 +1,622 @@
/*
* MIT License
*
* 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 "interval_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
#include "watch_buzzer.h"
typedef enum {
interval_setting_0_timer_idx,
interval_setting_1_clear_yn,
interval_setting_2_warmup_minutes,
interval_setting_3_warmup_seconds,
interval_setting_4_work_minutes,
interval_setting_5_work_seconds,
interval_setting_6_work_rounds,
interval_setting_7_break_minutes,
interval_setting_8_break_seconds,
interval_setting_9_full_rounds,
interval_setting_10_cooldown_minutes,
interval_setting_11_cooldown_seconds,
interval_setting_max
} interval_setting_idx_t;
#define INTERVAL_FACE_STATE_DEFAULT "IT" // Interval Timer
#define INTERVAL_FACE_STATE_WARMUP "PR" // PRepare / warm up
#define INTERVAL_FACE_STATE_WORK "WO" // WOrk
#define INTERVAL_FACE_STATE_BREAK "BR" // BReak
#define INTERVAL_FACE_STATE_COOLDOWN "CD" // CoolDown
// Define some default timer settings. Each timer is described in an array like this:
// 1. warm-up seconds,
// 2. work time (seconds/minutes)
// 3. break time (seconds/minutes)
// 4. full rounds (0 = no limit)
// 5. cooldown seconds
// Work time and break time: positive number = seconds, negative number = minutes
static const int8_t _default_timers[6][5] = {{0, 40, 20, 0, 0},
{0, 45, 15, 0, 0},
{10, 20, 10, 8, 10},
{0, 35, 0, 0, 0},
{0, -25, -5, 0, 0},
{0, -20, -5, 0, 0}};
static const uint8_t _intro_segdata[4][2] = {{1, 8}, {0, 8}, {0, 7}, {1, 7}};
static const uint8_t _blink_idx[] = {3, 9, 4, 6, 4, 6, 8, 4, 6, 8, 4, 6};
static const uint8_t _setting_page_idx[] = {1, 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4};
static const int8_t _sound_seq_warmup[] = {BUZZER_NOTE_F6, 8, BUZZER_NOTE_REST, 1, -2, 3, 0};
static const int8_t _sound_seq_work[] = {BUZZER_NOTE_F6, 8, BUZZER_NOTE_REST, 1, -2, 2, BUZZER_NOTE_C7, 24, 0};
static const int8_t _sound_seq_break[] = {BUZZER_NOTE_B6, 15, BUZZER_NOTE_REST, 1, -2, 1, BUZZER_NOTE_B6, 16, 0};
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};
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;
if (*value >= max) *value = 0;
}
static uint32_t _get_now_ts() {
// returns the current date time as unix timestamp
watch_date_time_t now = watch_rtc_get_date_time();
return watch_utility_date_time_to_unix_time(now, 0);
}
static inline void _button_beep() {
// play a beep as confirmation for a button press (if applicable)
if (movement_button_should_sound()) watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
}
static void _timer_write_info(interval_face_state_t *state, char *buf, uint8_t timer_page) {
// fill display string with requested timer information
switch (timer_page) {
case 0:
// clear timer?
sprintf(buf, "%2s %1dCLEARn", INTERVAL_FACE_STATE_DEFAULT, state->timer_idx + 1);
if (_erase_timer_flag) buf[9] = 'y';
watch_clear_colon();
break;
case 1:
// warmup time info
sprintf(buf, "%2s %1d%02d%02d ", INTERVAL_FACE_STATE_WARMUP, state->timer_idx + 1,
state->timer[state->timer_idx].warmup_minutes,
state->timer[state->timer_idx].warmup_seconds);
break;
case 2:
// work interval info
sprintf(buf, "%2s %1d%02d%02d%2d", INTERVAL_FACE_STATE_WORK, state->timer_idx + 1,
state->timer[state->timer_idx].work_minutes,
state->timer[state->timer_idx].work_seconds,
state->timer[state->timer_idx].work_rounds);
break;
case 3:
// break interval info
sprintf(buf, "%2s %1d%02d%02d%2d", INTERVAL_FACE_STATE_BREAK, state->timer_idx + 1,
state->timer[state->timer_idx].break_minutes,
state->timer[state->timer_idx].break_seconds,
state->timer[state->timer_idx].full_rounds);
if (!state->timer[state->timer_idx].full_rounds) buf[9] = '-';
break;
case 4:
// cooldown time info
sprintf(buf, "%2s %1d%02d%02d ", INTERVAL_FACE_STATE_COOLDOWN ,state->timer_idx + 1,
state->timer[state->timer_idx].cooldown_minutes,
state->timer[state->timer_idx].cooldown_seconds);
break;
default:
break;
}
}
static void _face_draw(interval_face_state_t *state, uint8_t subsecond) {
// draws current face state
if (!state->is_active) return;
char buf[14];
buf[0] = 0;
uint8_t tmp;
if (state->face_state == interval_state_waiting && _ticks >= 0) {
// play info slideshow for current timer
int8_t ticks = _ticks % 12;
if (ticks == 0) {
if ((state->timer[state->timer_idx].warmup_minutes + state->timer[state->timer_idx].warmup_seconds) == 0) {
// skip warmup info if there is none for this timer
ticks = 3;
_ticks += 3;
}
}
tmp = ticks / 3 + 1;
_timer_write_info(state, buf, tmp);
// don't show '1 round' when displaying workout time to avoid detail overload
if (tmp == 2 && state->timer[state->timer_idx].work_rounds == 1) buf[9] = ' ';
// blink colon
if (subsecond % 2 == 0 && _ticks < 24) watch_clear_colon();
else watch_set_colon();
} else if (state->face_state == interval_state_setting) {
if (_setting_idx == interval_setting_0_timer_idx) {
if ((state->timer[state->timer_idx].warmup_minutes + state->timer[state->timer_idx].warmup_seconds) == 0)
tmp = 1;
else
tmp = 2;
} else {
tmp = _setting_page_idx[_setting_idx];
}
_timer_write_info(state, buf, tmp);
// blink at cursor position
if (subsecond % 2 && _ticks != -2) {
buf[_blink_idx[_setting_idx]] = ' ';
if (_blink_idx[_setting_idx] % 2 == 0) buf[_blink_idx[_setting_idx] + 1] = ' ';
}
// show lap indicator only when rounds are set
if (_setting_idx == interval_setting_6_work_rounds || _setting_idx == interval_setting_9_full_rounds)
watch_set_indicator(WATCH_INDICATOR_LAP);
else
watch_clear_indicator(WATCH_INDICATOR_LAP);
} else if (state->face_state == interval_state_running || state->face_state == interval_state_pausing) {
tmp = _timer_full_round;
switch (_timer_run_state) {
case 0:
sprintf(buf, INTERVAL_FACE_STATE_WARMUP);
break;
case 1:
sprintf(buf, INTERVAL_FACE_STATE_WORK);
if (state->timer[state->timer_idx].work_rounds > 1) tmp = _timer_work_round;
break;
case 2:
sprintf(buf, INTERVAL_FACE_STATE_BREAK);
break;
case 3:
sprintf(buf, INTERVAL_FACE_STATE_COOLDOWN);
break;
default:
break;
}
div_t delta;
if (state->face_state == interval_state_pausing) {
// pausing
delta = div(_target_ts - _paused_ts, 60);
// blink the bell icon
if (_now_ts % 2) watch_set_indicator(WATCH_INDICATOR_BELL);
else watch_clear_indicator(WATCH_INDICATOR_BELL);
} else
// running
delta = div(_target_ts - _now_ts, 60);
sprintf(&buf[2], " %1d%02d%02d%2d", state->timer_idx + 1, delta.quot, delta.rem, tmp + 1);
}
// write out to lcd
if (buf[0]) {
watch_display_character(buf[0], 0);
watch_display_character(buf[1], 1);
// set the bar for the i-like symbol on position 2
watch_set_pixel(2, 9);
// display the rest of the string
watch_display_string(&buf[3], 3);
}
}
static void _initiate_setting(interval_face_state_t *state, uint8_t subsecond) {
state->face_state = interval_state_setting;
_setting_idx = interval_setting_0_timer_idx;
_ticks = 0;
_erase_timer_flag = false;
watch_set_colon();
movement_request_tick_frequency(4);
_face_draw(state, subsecond);
}
static void _resume_setting(interval_face_state_t *state, uint8_t subsecond) {
state->face_state = interval_state_waiting;
_ticks = 0;
_face_draw(state, subsecond);
movement_request_tick_frequency(2);
watch_clear_indicator(WATCH_INDICATOR_LAP);
}
static void _abort_quick_ticks() {
if (_ticks == -2) {
_ticks = -1;
movement_request_tick_frequency(4);
}
}
static void _handle_alarm_button(interval_face_state_t *state) {
// handles the alarm button press and alters the corresponding timer settings
switch (_setting_idx) {
case interval_setting_0_timer_idx:
_inc_uint8(&state->timer_idx, 1, INTERVAL_TIMERS);
_erase_timer_flag = false;
break;
case interval_setting_1_clear_yn:
_erase_timer_flag ^= 1;
break;
case interval_setting_2_warmup_minutes:
_inc_uint8(&state->timer[state->timer_idx].warmup_minutes, 1, 60);
break;
case interval_setting_3_warmup_seconds:
_inc_uint8(&state->timer[state->timer_idx].warmup_seconds, 5, 60);
break;
case interval_setting_4_work_minutes:
_inc_uint8(&state->timer[state->timer_idx].work_minutes, 1, 60);
if (state->timer[state->timer_idx].work_rounds == 0) state->timer[state->timer_idx].work_rounds = 1;
break;
case interval_setting_5_work_seconds:
_inc_uint8(&state->timer[state->timer_idx].work_seconds, 5, 60);
if (state->timer[state->timer_idx].work_rounds == 0) state->timer[state->timer_idx].work_rounds = 1;
break;
case interval_setting_6_work_rounds:
_inc_uint8(&state->timer[state->timer_idx].work_rounds, 1, 100);
break;
case interval_setting_7_break_minutes:
_inc_uint8(&state->timer[state->timer_idx].break_minutes, 1, 60);
break;
case interval_setting_8_break_seconds:
_inc_uint8(&state->timer[state->timer_idx].break_seconds, 5, 60);
break;
case interval_setting_9_full_rounds:
_inc_uint8(&state->timer[state->timer_idx].full_rounds, 1, 100);
break;
case interval_setting_10_cooldown_minutes:
_inc_uint8(&state->timer[state->timer_idx].cooldown_minutes, 1, 60);
break;
case interval_setting_11_cooldown_seconds:
_inc_uint8(&state->timer[state->timer_idx].cooldown_seconds, 5, 60);
break;
default:
break;
}
}
static void _set_next_timestamp(interval_face_state_t *state) {
// set next timestamp for the running timer, set background task and pay sound sequence
uint16_t delta = 0;
int8_t *sound_seq;
interval_timer_setting_t timer = state->timer[state->timer_idx];
switch (_timer_run_state) {
case 0:
delta = timer.warmup_minutes * 60 + timer.warmup_seconds;
sound_seq = (int8_t *)_sound_seq_warmup;
break;
case 1:
delta = timer.work_minutes * 60 + timer.work_seconds;
sound_seq = (int8_t *)_sound_seq_work;
break;
case 2:
delta = timer.break_minutes * 60 + timer.break_seconds;
sound_seq = (int8_t *)_sound_seq_break;
break;
case 3:
delta = timer.cooldown_minutes * 60 + timer.cooldown_seconds;
sound_seq = (int8_t *)_sound_seq_cooldown;
break;
default:
sound_seq = NULL;
break;
}
// failsafe
if (delta <= 0) delta = 1;
_target_ts += delta;
// schedule next background task
watch_date_time_t target_dt = watch_utility_date_time_from_unix_time(_target_ts, 0);
movement_schedule_background_task_for_face(state->face_idx, target_dt);
// play sound
watch_buzzer_play_sequence(sound_seq, NULL);
}
static inline bool _is_timer_empty(interval_timer_setting_t *timer) {
// checks if a timer is empty
return (timer->warmup_minutes + timer->warmup_seconds
+ timer->work_minutes + timer->work_seconds
+ timer->break_minutes + timer->break_seconds
+ timer->cooldown_minutes + timer->cooldown_seconds == 0);
}
static void _init_timer_info(interval_face_state_t *state) {
state->face_state = interval_state_waiting;
_ticks = 0;
if (state->is_active) movement_request_tick_frequency(2);
}
static void _abort_running_timer() {
_timer_work_round = _timer_full_round = 0;
_timer_run_state = 0;
movement_cancel_background_task();
watch_clear_indicator(WATCH_INDICATOR_BELL);
watch_buzzer_play_note(BUZZER_NOTE_C8, 100);
}
static void _resume_paused_timer(interval_face_state_t *state) {
// resume paused timer
_now_ts = _get_now_ts();
_target_ts += _now_ts - _paused_ts;
watch_date_time_t target_dt = watch_utility_date_time_from_unix_time(_target_ts, 0);
movement_schedule_background_task_for_face(state->face_idx, target_dt);
state->face_state = interval_state_running;
watch_set_indicator(WATCH_INDICATOR_BELL);
}
void interval_face_setup(uint8_t watch_face_index, void **context_ptr) {
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(interval_face_state_t));
interval_face_state_t *state = (interval_face_state_t *)*context_ptr;
memset(*context_ptr, 0, sizeof(interval_face_state_t));
state->face_idx = watch_face_index;
// somehow the memset above doesn't to the trick. So set the state explicitly
state->face_state = interval_state_waiting;
for (uint8_t i = 0; i < INTERVAL_TIMERS; i++) state->timer[i].work_rounds = 1;
// set up default timers
for (uint8_t i = 0; i < 6; i++) {
state->timer[i].warmup_seconds = _default_timers[i][0];
if (_default_timers[i][1] < 0) state->timer[i].work_minutes = -_default_timers[i][1];
else state->timer[i].work_seconds = _default_timers[i][1];
state->timer[i].work_rounds = 1;
if (_default_timers[i][2] < 0) state->timer[i].break_minutes = -_default_timers[i][2];
else state->timer[i].break_seconds = _default_timers[i][2];
state->timer[i].full_rounds = _default_timers[i][3];
state->timer[i].cooldown_seconds = _default_timers[i][4];
}
}
}
void interval_face_activate(void *context) {
interval_face_state_t *state = (interval_face_state_t *)context;
_erase_timer_flag = false;
state->is_active = true;
if (state->face_state <= interval_state_waiting) {
// initiate the intro loop
state->face_state = interval_state_intro;
_ticks = 0;
movement_request_tick_frequency(8);
} else watch_set_colon();
}
void interval_face_resign(void *context) {
interval_face_state_t *state = (interval_face_state_t *)context;
if (state->face_state <= interval_state_setting) state->face_state = interval_state_waiting;
watch_set_led_off();
movement_request_tick_frequency(1);
state->is_active = false;
}
bool interval_face_loop(movement_event_t event, void *context) {
interval_face_state_t *state = (interval_face_state_t *)context;
interval_timer_setting_t *timer = &state->timer[state->timer_idx];
switch (event.event_type) {
case EVENT_TICK:
if (state->face_state == interval_state_intro) {
// play intro animation so the wearer knows the face
if (_ticks == 4) {
// transition to default view of current interval slot
watch_set_colon();
_init_timer_info(state);
_face_draw(state, event.subsecond);
break;
}
watch_set_pixel(_intro_segdata[_ticks][0], _intro_segdata[_ticks][1]);
_ticks++;
} else if (state->face_state == interval_state_waiting && _ticks >= 0) {
// play information slideshow for current interval timer
_ticks++;
if ((_ticks % 12 == 9) && (timer->cooldown_minutes + timer->cooldown_seconds == 0)) _ticks += 3;
if (_ticks > 24) _ticks = -1;
else _face_draw(state, event.subsecond);
} else if (state->face_state == interval_state_setting) {
if (_ticks == -2) {
// fast counting
_handle_alarm_button(state);
}
_face_draw(state, event.subsecond);
} else if (state->face_state == interval_state_running || state->face_state == interval_state_pausing) {
_now_ts = _get_now_ts();
_face_draw(state, event.subsecond);
}
break;
case EVENT_ACTIVATE:
watch_display_string(INTERVAL_FACE_STATE_DEFAULT, 0);
if (state->face_state) _face_draw(state, event.subsecond);
break;
case EVENT_LIGHT_BUTTON_UP:
if (state->face_state == interval_state_setting) {
if (_setting_idx == interval_setting_0_timer_idx) {
// skip clear page if timer is empty
if (_is_timer_empty(timer)) _setting_idx = interval_setting_1_clear_yn;
} else if (_setting_idx == interval_setting_1_clear_yn) {
watch_set_colon();
if (_erase_timer_flag) {
// clear the current timer
memset((void *)timer, 0, sizeof(interval_timer_setting_t));
// play a short beep as confirmation
watch_buzzer_play_note(BUZZER_NOTE_C8, 70);
}
} else if (_setting_idx == interval_setting_9_full_rounds && !timer->full_rounds) {
// skip cooldown if full rounds are not limited
_setting_idx = interval_setting_11_cooldown_seconds;
}
_setting_idx += 1;
if (_setting_idx == interval_setting_max) {
// we have done a full settings circle: resume setting
_resume_setting(state, event.subsecond);
} else
_face_draw(state, event.subsecond);
} else {
movement_illuminate_led();
}
break;
case EVENT_LIGHT_LONG_PRESS:
_button_beep(settings);
if (state->face_state == interval_state_setting) {
_resume_setting(state, event.subsecond);
} else {
if (state->face_state >= interval_state_running ) _abort_running_timer();
_initiate_setting(state, event.subsecond);
}
break;
case EVENT_ALARM_BUTTON_UP:
switch (state->face_state) {
case interval_state_waiting:
// cycle through timers
_inc_uint8(&state->timer_idx, 1, INTERVAL_TIMERS);
_ticks = 0;
_face_draw(state, event.subsecond);
break;
case interval_state_setting:
// alter timer settings
_abort_quick_ticks();
_handle_alarm_button(state);
break;
case interval_state_running:
// pause timer
_button_beep(settings);
_paused_ts = _get_now_ts();
state->face_state = interval_state_pausing;
movement_cancel_background_task();
_face_draw(state, event.subsecond);
break;
case interval_state_pausing:
// resume paused timer
_button_beep(settings);
_resume_paused_timer(state);
_face_draw(state, event.subsecond);
break;
default:
break;
}
break;
case EVENT_ALARM_LONG_PRESS:
if (state->face_state == interval_state_setting && _setting_idx != interval_setting_1_clear_yn) {
// initiate quick counting
_ticks = -2;
movement_request_tick_frequency(8);
break;
} else if (state->face_state <= interval_state_waiting) {
if (_is_timer_empty(timer)) {
// jump back to timer #1
_button_beep(settings);
state->timer_idx = 0;
_init_timer_info(state);
} else {
// set initial state and start timer
_timer_work_round = _timer_full_round = 0;
if (timer->warmup_minutes + timer->warmup_seconds) _timer_run_state = 0;
else if (timer->work_minutes + timer->work_seconds) _timer_run_state = 1;
else if (timer->break_minutes + timer->break_seconds) _timer_run_state = 2;
else if (timer->cooldown_minutes + timer->cooldown_seconds) _timer_run_state = 3;
movement_request_tick_frequency(1);
_now_ts = _get_now_ts();
_target_ts = _now_ts;
_set_next_timestamp(state);
state->face_state = interval_state_running;
watch_set_indicator(WATCH_INDICATOR_BELL);
watch_set_colon();
}
} else if (state->face_state == interval_state_running) {
// stop the timer
_abort_running_timer();
_init_timer_info(state);
} else if (state->face_state == interval_state_pausing) {
// resume paused timer
_button_beep(settings);
_resume_paused_timer(state);
}
_face_draw(state, event.subsecond);
break;
case EVENT_ALARM_LONG_UP:
_abort_quick_ticks();
break;
case EVENT_BACKGROUND_TASK:
// find the next timestamp or end the timer
if (_timer_run_state == 0) {
// warmup finished
if (timer->work_minutes + timer->work_seconds) _timer_run_state = 1;
else if (timer->break_minutes + timer->break_seconds) _timer_run_state = 2;
else if (timer->cooldown_minutes + timer->cooldown_seconds) _timer_run_state = 3;
else _timer_run_state = 4;
} else if (_timer_run_state == 1) {
// work finished
_timer_work_round++;
if (_timer_work_round == timer->work_rounds) {
_timer_work_round = 0;
if (timer->break_minutes + timer->break_seconds && (timer->full_rounds == 0
|| (timer->full_rounds && _timer_full_round + 1 < timer->full_rounds))) _timer_run_state = 2;
else {
_timer_full_round++;
if (timer->full_rounds && _timer_full_round == timer->full_rounds) {
if (timer->cooldown_minutes + timer->cooldown_seconds) _timer_run_state = 3;
else _timer_run_state = 4;
} else _timer_run_state = 1;
}
}
} else if (_timer_run_state == 2) {
// break finished
_timer_full_round++;
_timer_work_round = 0;
if (timer->full_rounds && _timer_full_round == timer->full_rounds) {
if (timer->cooldown_minutes + timer->cooldown_seconds) _timer_run_state = 3;
else _timer_run_state = 4;
_timer_full_round--;
} else {
if (timer->work_minutes + timer->work_seconds) _timer_run_state = 1;
}
} else if (_timer_run_state == 3)
// cooldown finished
_timer_run_state = 4;
// set next timestamp or play final sound sequence
if (_timer_run_state < 4) {
// transition to next timer phase
_set_next_timestamp(state);
} else {
// timer has finished
state->face_state = interval_state_waiting;
_init_timer_info(state);
_face_draw(state, event.subsecond);
watch_buzzer_play_sequence((int8_t *)_sound_seq_finish, NULL);
}
break;
case EVENT_TIMEOUT:
if (state->face_state != interval_state_running) movement_move_to_face(0);
break;
case EVENT_LIGHT_BUTTON_DOWN:
// don't light up every time light is hit
break;
default:
movement_default_loop_handler(event);
break;
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More