'movement' -> 'legacy' to signal things we still need to bring in
This commit is contained in:
43
legacy/alt_fw/alt_time.h
Normal file
43
legacy/alt_fw/alt_time.h
Normal 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
45
legacy/alt_fw/backer.h
Normal 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_
|
||||
63
legacy/alt_fw/deep_space_now.h
Normal file
63
legacy/alt_fw/deep_space_now.h
Normal 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
45
legacy/alt_fw/focus.h
Normal 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_
|
||||
45
legacy/alt_fw/the_athlete.h
Normal file
45
legacy/alt_fw/the_athlete.h
Normal 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_
|
||||
46
legacy/alt_fw/the_backpacker.h
Normal file
46
legacy/alt_fw/the_backpacker.h
Normal 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_
|
||||
47
legacy/alt_fw/the_stargazer.h
Normal file
47
legacy/alt_fw/the_stargazer.h
Normal 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
45
legacy/alt_fw/timers.h
Normal 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
21
legacy/lib/TOTP/LICENSE
Normal 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
68
legacy/lib/TOTP/README.md
Normal 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
69
legacy/lib/TOTP/TOTP.c
Normal 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
21
legacy/lib/TOTP/TOTP.h
Normal 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
27
legacy/lib/TOTP/example.c
Normal 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
398
legacy/lib/TOTP/sha1.c
Normal 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
98
legacy/lib/TOTP/sha1.h
Normal 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
372
legacy/lib/TOTP/sha256.c
Normal 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
112
legacy/lib/TOTP/sha256.h
Normal 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
422
legacy/lib/TOTP/sha512.c
Normal 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
119
legacy/lib/TOTP/sha512.h
Normal 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 */
|
||||
9
legacy/lib/astrolib/LICENSE.txt
Executable file
9
legacy/lib/astrolib/LICENSE.txt
Executable 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.
|
||||
9
legacy/lib/astrolib/README.md
Normal file
9
legacy/lib/astrolib/README.md
Normal 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.
|
||||
555
legacy/lib/astrolib/astrolib.c
Normal file
555
legacy/lib/astrolib/astrolib.c
Normal 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;
|
||||
}
|
||||
87
legacy/lib/astrolib/astrolib.h
Normal file
87
legacy/lib/astrolib/astrolib.h
Normal 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
221
legacy/lib/base32/base32.c
Normal 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;
|
||||
}
|
||||
}
|
||||
66
legacy/lib/base32/base32.h
Normal file
66
legacy/lib/base32/base32.h
Normal 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
113
legacy/lib/morsecalc/calc.c
Normal 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;
|
||||
}
|
||||
44
legacy/lib/morsecalc/calc.h
Normal file
44
legacy/lib/morsecalc/calc.h
Normal 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
|
||||
240
legacy/lib/morsecalc/calc_fns.c
Normal file
240
legacy/lib/morsecalc/calc_fns.c
Normal 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;
|
||||
}
|
||||
|
||||
128
legacy/lib/morsecalc/calc_fns.h
Normal file
128
legacy/lib/morsecalc/calc_fns.h
Normal 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},
|
||||
};
|
||||
144
legacy/lib/morsecalc/calc_strtof.c
Normal file
144
legacy/lib/morsecalc/calc_strtof.c
Normal 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;
|
||||
}
|
||||
|
||||
147
legacy/lib/morsecalc/morsecalc_display.c
Normal file
147
legacy/lib/morsecalc/morsecalc_display.c
Normal 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;
|
||||
}
|
||||
|
||||
35
legacy/lib/morsecalc/morsecalc_display.h
Normal file
35
legacy/lib/morsecalc/morsecalc_display.h
Normal 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);
|
||||
|
||||
66
legacy/lib/morsecalc/test_morsecalc.c
Normal file
66
legacy/lib/morsecalc/test_morsecalc.c
Normal 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;
|
||||
}
|
||||
3704
legacy/lib/smallchesslib/smallchesslib.h
Normal file
3704
legacy/lib/smallchesslib/smallchesslib.h
Normal file
File diff suppressed because it is too large
Load Diff
5
legacy/lib/vsop87/LICENSE.txt
Executable file
5
legacy/lib/vsop87/LICENSE.txt
Executable 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
143
legacy/lib/vsop87/README.md
Executable 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
|
||||

|
||||
|
||||
### Venus
|
||||

|
||||
|
||||
### Earth-Moon Barrycenter
|
||||

|
||||
|
||||
### Earth-Moon Barrycenter (zoomed)
|
||||

|
||||
|
||||
### Mars
|
||||

|
||||
|
||||
### Jupiter
|
||||

|
||||
|
||||
### Saturn
|
||||

|
||||
|
||||
### Saturn (zoomed)
|
||||

|
||||
|
||||
### Uranus
|
||||

|
||||
|
||||
### Uranus (zoomed)
|
||||

|
||||
|
||||
### Neptune
|
||||

|
||||
|
||||
### Neptune (zoomed)
|
||||

|
||||
1019
legacy/lib/vsop87/vsop87a_micro.c
Executable file
1019
legacy/lib/vsop87/vsop87a_micro.c
Executable file
File diff suppressed because it is too large
Load Diff
17
legacy/lib/vsop87/vsop87a_micro.h
Executable file
17
legacy/lib/vsop87/vsop87a_micro.h
Executable 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
2048
legacy/lib/vsop87/vsop87a_milli.c
Executable file
File diff suppressed because it is too large
Load Diff
17
legacy/lib/vsop87/vsop87a_milli.h
Executable file
17
legacy/lib/vsop87/vsop87a_milli.h
Executable 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
128
legacy/movement_faces.h
Normal 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_
|
||||
231
legacy/watch_faces/clock/close_enough_clock_face.c
Normal file
231
legacy/watch_faces/clock/close_enough_clock_face.c
Normal 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;
|
||||
}
|
||||
62
legacy/watch_faces/clock/close_enough_clock_face.h
Normal file
62
legacy/watch_faces/clock/close_enough_clock_face.h
Normal 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_
|
||||
136
legacy/watch_faces/clock/day_night_percentage_face.c
Normal file
136
legacy/watch_faces/clock/day_night_percentage_face.c
Normal 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;
|
||||
}
|
||||
|
||||
66
legacy/watch_faces/clock/day_night_percentage_face.h
Normal file
66
legacy/watch_faces/clock/day_night_percentage_face.h
Normal 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_
|
||||
|
||||
176
legacy/watch_faces/clock/decimal_time_face.c
Normal file
176
legacy/watch_faces/clock/decimal_time_face.c
Normal 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() {
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
71
legacy/watch_faces/clock/decimal_time_face.h
Normal file
71
legacy/watch_faces/clock/decimal_time_face.h
Normal 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_
|
||||
242
legacy/watch_faces/clock/french_revolutionary_face.c
Normal file
242
legacy/watch_faces/clock/french_revolutionary_face.c
Normal 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;
|
||||
}
|
||||
84
legacy/watch_faces/clock/french_revolutionary_face.h
Normal file
84
legacy/watch_faces/clock/french_revolutionary_face.h
Normal 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_
|
||||
|
||||
163
legacy/watch_faces/clock/mars_time_face.c
Normal file
163
legacy/watch_faces/clock/mars_time_face.c
Normal 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;
|
||||
}
|
||||
|
||||
84
legacy/watch_faces/clock/mars_time_face.h
Normal file
84
legacy/watch_faces/clock/mars_time_face.h
Normal 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_
|
||||
|
||||
114
legacy/watch_faces/clock/minimal_clock_face.c
Normal file
114
legacy/watch_faces/clock/minimal_clock_face.c
Normal 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.
|
||||
}
|
||||
|
||||
57
legacy/watch_faces/clock/minimal_clock_face.h
Normal file
57
legacy/watch_faces/clock/minimal_clock_face.h
Normal 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_
|
||||
|
||||
238
legacy/watch_faces/clock/minute_repeater_decimal_face.c
Normal file
238
legacy/watch_faces/clock/minute_repeater_decimal_face.c
Normal 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;
|
||||
}
|
||||
84
legacy/watch_faces/clock/minute_repeater_decimal_face.h
Normal file
84
legacy/watch_faces/clock/minute_repeater_decimal_face.h
Normal 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_
|
||||
221
legacy/watch_faces/clock/repetition_minute_face.c
Normal file
221
legacy/watch_faces/clock/repetition_minute_face.c
Normal 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;
|
||||
}
|
||||
83
legacy/watch_faces/clock/repetition_minute_face.h
Normal file
83
legacy/watch_faces/clock/repetition_minute_face.h
Normal 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_
|
||||
222
legacy/watch_faces/clock/simple_clock_bin_led_face.c
Normal file
222
legacy/watch_faces/clock/simple_clock_bin_led_face.c
Normal 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;
|
||||
}
|
||||
82
legacy/watch_faces/clock/simple_clock_bin_led_face.h
Normal file
82
legacy/watch_faces/clock/simple_clock_bin_led_face.h
Normal 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_
|
||||
|
||||
156
legacy/watch_faces/clock/weeknumber_clock_face.c
Normal file
156
legacy/watch_faces/clock/weeknumber_clock_face.c
Normal 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;
|
||||
}
|
||||
61
legacy/watch_faces/clock/weeknumber_clock_face.h
Normal file
61
legacy/watch_faces/clock/weeknumber_clock_face.h
Normal 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_
|
||||
374
legacy/watch_faces/clock/world_clock2_face.c
Normal file
374
legacy/watch_faces/clock/world_clock2_face.c
Normal 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;
|
||||
}
|
||||
122
legacy/watch_faces/clock/world_clock2_face.h
Normal file
122
legacy/watch_faces/clock/world_clock2_face.h
Normal 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_ */
|
||||
207
legacy/watch_faces/clock/wyoscan_face.c
Normal file
207
legacy/watch_faces/clock/wyoscan_face.c
Normal 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
|
||||
|
||||
You’ll 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.
|
||||
}
|
||||
|
||||
91
legacy/watch_faces/clock/wyoscan_face.h
Normal file
91
legacy/watch_faces/clock/wyoscan_face.h
Normal 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_
|
||||
|
||||
722
legacy/watch_faces/complication/activity_face.c
Normal file
722
legacy/watch_faces/complication/activity_face.c
Normal 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);
|
||||
}
|
||||
88
legacy/watch_faces/complication/activity_face.h
Normal file
88
legacy/watch_faces/complication/activity_face.h
Normal 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_
|
||||
|
||||
270
legacy/watch_faces/complication/astronomy_face.c
Normal file
270
legacy/watch_faces/complication/astronomy_face.c
Normal 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;
|
||||
}
|
||||
109
legacy/watch_faces/complication/astronomy_face.h
Normal file
109
legacy/watch_faces/complication/astronomy_face.h
Normal 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, you’ll 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 Earth’s moon
|
||||
* MA - Mars
|
||||
* JU - Jupiter
|
||||
* SA - Saturn
|
||||
* UR - Uranus
|
||||
* NE - Neptune
|
||||
*
|
||||
* Once you’ve 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_
|
||||
102
legacy/watch_faces/complication/blinky_face.c
Normal file
102
legacy/watch_faces/complication/blinky_face.c
Normal 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();
|
||||
}
|
||||
75
legacy/watch_faces/complication/blinky_face.h
Normal file
75
legacy/watch_faces/complication/blinky_face.h
Normal 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 battery’s 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_
|
||||
204
legacy/watch_faces/complication/breathing_face.c
Normal file
204
legacy/watch_faces/complication/breathing_face.c
Normal 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;
|
||||
}
|
||||
54
legacy/watch_faces/complication/breathing_face.h
Normal file
54
legacy/watch_faces/complication/breathing_face.h
Normal 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_
|
||||
464
legacy/watch_faces/complication/butterfly_game_face.c
Normal file
464
legacy/watch_faces/complication/butterfly_game_face.c
Normal 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.
|
||||
}
|
||||
|
||||
125
legacy/watch_faces/complication/butterfly_game_face.h
Normal file
125
legacy/watch_faces/complication/butterfly_game_face.h
Normal 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_
|
||||
|
||||
264
legacy/watch_faces/complication/couch_to_5k_face.c
Normal file
264
legacy/watch_faces/complication/couch_to_5k_face.c
Normal 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.
|
||||
}
|
||||
|
||||
87
legacy/watch_faces/complication/couch_to_5k_face.h
Normal file
87
legacy/watch_faces/complication/couch_to_5k_face.h
Normal 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_
|
||||
|
||||
145
legacy/watch_faces/complication/counter_face.c
Normal file
145
legacy/watch_faces/complication/counter_face.c
Normal 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;
|
||||
}
|
||||
63
legacy/watch_faces/complication/counter_face.h
Normal file
63
legacy/watch_faces/complication/counter_face.h
Normal 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_
|
||||
143
legacy/watch_faces/complication/databank_face.c
Normal file
143
legacy/watch_faces/complication/databank_face.c
Normal 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;
|
||||
}
|
||||
60
legacy/watch_faces/complication/databank_face.h
Normal file
60
legacy/watch_faces/complication/databank_face.h
Normal 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_
|
||||
270
legacy/watch_faces/complication/day_one_face.c
Normal file
270
legacy/watch_faces/complication/day_one_face.c
Normal 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;
|
||||
}
|
||||
}
|
||||
83
legacy/watch_faces/complication/day_one_face.h
Normal file
83
legacy/watch_faces/complication/day_one_face.h
Normal 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 you’ve 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_
|
||||
626
legacy/watch_faces/complication/deadline_face.c
Normal file
626
legacy/watch_faces/complication/deadline_face.c
Normal 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;
|
||||
}
|
||||
65
legacy/watch_faces/complication/deadline_face.h
Normal file
65
legacy/watch_faces/complication/deadline_face.h
Normal 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_
|
||||
322
legacy/watch_faces/complication/discgolf_face.c
Normal file
322
legacy/watch_faces/complication/discgolf_face.c
Normal 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);
|
||||
}
|
||||
94
legacy/watch_faces/complication/discgolf_face.h
Normal file
94
legacy/watch_faces/complication/discgolf_face.h
Normal 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_
|
||||
331
legacy/watch_faces/complication/dual_timer_face.c
Normal file
331
legacy/watch_faces/complication/dual_timer_face.c
Normal 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.
|
||||
}
|
||||
|
||||
108
legacy/watch_faces/complication/dual_timer_face.h
Normal file
108
legacy/watch_faces/complication/dual_timer_face.h
Normal 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_
|
||||
|
||||
614
legacy/watch_faces/complication/endless_runner_face.c
Normal file
614
legacy/watch_faces/complication/endless_runner_face.c
Normal 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;
|
||||
}
|
||||
|
||||
62
legacy/watch_faces/complication/endless_runner_face.h
Normal file
62
legacy/watch_faces/complication/endless_runner_face.h
Normal 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_
|
||||
|
||||
73
legacy/watch_faces/complication/flashlight_face.c
Normal file
73
legacy/watch_faces/complication/flashlight_face.c
Normal 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();
|
||||
}
|
||||
59
legacy/watch_faces/complication/flashlight_face.h
Normal file
59
legacy/watch_faces/complication/flashlight_face.h
Normal 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_
|
||||
|
||||
359
legacy/watch_faces/complication/geomancy_face.c
Normal file
359
legacy/watch_faces/complication/geomancy_face.c
Normal 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);
|
||||
}
|
||||
99
legacy/watch_faces/complication/geomancy_face.h
Normal file
99
legacy/watch_faces/complication/geomancy_face.h
Normal 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_
|
||||
|
||||
154
legacy/watch_faces/complication/habit_face.c
Normal file
154
legacy/watch_faces/complication/habit_face.c
Normal 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;
|
||||
}
|
||||
53
legacy/watch_faces/complication/habit_face.h
Normal file
53
legacy/watch_faces/complication/habit_face.h
Normal 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_
|
||||
393
legacy/watch_faces/complication/higher_lower_game_face.c
Executable file
393
legacy/watch_faces/complication/higher_lower_game_face.c
Executable 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.
|
||||
}
|
||||
106
legacy/watch_faces/complication/higher_lower_game_face.h
Executable file
106
legacy/watch_faces/complication/higher_lower_game_face.h
Executable 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_
|
||||
622
legacy/watch_faces/complication/interval_face.c
Normal file
622
legacy/watch_faces/complication/interval_face.c
Normal 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
Reference in New Issue
Block a user