Skip to content

Circadian Wellness Clock: Initial submission for review #3878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jun 13, 2025

Conversation

spycat111
Copy link
Contributor

@spycat111 spycat111 commented Jun 11, 2025

This PR adds the Circadian Wellness Clock app for Bangle.js 2 (Author: Jakub Tencl).

App features:

  • Displays biological time (BioTime), CRS (Circadian Rhythm Score), Steps, Mood, and Light Exposure
  • Uses step, HR, and light data from Bangle sensors
  • Sleep window and hydration reminders
  • User settings menu (theme, notifications, calibration)
  • Optimized for memory, trims history arrays, and minimizes computation
  • Main screen shows CRS, Steps, Mood, LightExp, and color header

Request:

  • Please review for code style, Espruino/Bangle best practices, memory management, and usability.
  • Feedback on UI layout or optimizations welcome!

Thank you!

@spycat111
Copy link
Contributor Author

This pull request adds the initial version of the Circadian Wellness Clock app for Bangle.js 2. The app implements a wearable circadian rhythm scoring system for estimating biological time, based on physiological and environmental data.

Features:

  • Calculates a Circadian Rhythm Score (CRS) using activity, heart rate, light exposure, and sleep timing.
  • Estimates Biological Time from CRS and shows optimal sleep window.
  • Provides alerts and a visual daily trend.
  • Supports export and user settings.

Authorship & Intellectual Property:

  • Author: Jakub Tencl, Ph.D.
  • This app is based on UK Patent Application GB2315743.6 ("Wearable Circadian Rhythm Score (CRS) System for Estimating Biological Time").

@thyttan
Copy link
Collaborator

thyttan commented Jun 11, 2025

See notes on formatting the ChangeLog here: https://github.com/espruino/BangleApps?tab=readme-ov-file#changelog

Example the messagegui app ChangeLog: https://github.com/espruino/BangleApps/blob/master/apps/messagegui/ChangeLog

@thyttan thyttan assigned gfwilliams and unassigned gfwilliams Jun 11, 2025
@thyttan thyttan requested a review from gfwilliams June 11, 2025 22:59
@thyttan
Copy link
Collaborator

thyttan commented Jun 11, 2025

Hi @spycat111 - thanks for contributing!

@gfwilliams would you be able to look at this one? There seems to also be something strange with the sanitychecker and I wont have the time to try and understand it for a while.

@spycat111
Copy link
Contributor Author

Unfortunately, I don't know what I should fix based on Error: Process completed with exit code 7.

However, I tested the app on my watch and it works.

@thyttan
Copy link
Collaborator

thyttan commented Jun 11, 2025

Yes - thanks for the fixes so far. I don't know either. Let's see if Gordon can look at it tomorrow. ☀️

@spycat111
Copy link
Contributor Author

I suspect that this might have been the issue which I fixed:

"storage": [
{ "name": "crsclock.app.js", "url": "crsclock.js" },
{ "name": "crsclock.info", "url": "metadata.json" },
{ "name": "crsclock.img", "url": "icon.png" }
]

@gfwilliams
Copy link
Member

Hi - you're right - the metadata doesn't have a storage field. Looks like you added it (which was great) but then took it out though?

The metadata in general looks like a very different format to what's expected though - see https://github.com/espruino/BangleApps?tab=readme-ov-file#metadatajson-format

Or the example metadata for a clock is a good start: https://github.com/espruino/BangleApps/blob/111d80a8dea48fd5b9fcd39fe9f53e9a123e1062/apps/_example_clock/metadata.json

So you have author/src/patent/changelog fields that you don't need and which might be causing problems - and the icon needs converting to JS. I've just pushed some changes to the metadata itself. But you'll have to add app-icon.js based on https://github.com/espruino/BangleApps?tab=readme-ov-file#adding-your-app-to-the-menu (copy your existing icon to 48px wide and then convert with that tool linked)

Out of interest, how did you come up with the contents of the metadata file? Was it AI? It seems it wasn't based on any existing apps...


const uiOpts = {
mode: "custom",
clock: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? is this not meant to be a clock? Or you're doing this so that the button can be used to launch the menu

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. uiOpts.clock is set to false so the app can render a custom biological-time display and use BTN1 for its menu.

drawClock();

setInterval(() => {
if (Bangle.isLCDOn()) drawClock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you probably don't care if the LCD is on as the Bangle.js 2 LCD always is?

I'd probably change this logic slightly - most clocks do: https://www.espruino.com/Bangle.js+Clock+Font#simple-clock

// schedule a draw for the next minute
function queueDraw() {
  if (drawTimeout) clearTimeout(drawTimeout);
  drawTimeout = setTimeout(function() {
    drawTimeout = undefined;
    draw();
  }, 60000 - (Date.now() % 60000));
}

Which ensures that the clock will always redraw exactly on the minute - using a simple setInterval could mean the time shown is up to 59 seconds out

Copy link
Member

@gfwilliams gfwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good (apart from needing the icon I mentioned) - I'd be a bit concerned that pretty much all clocks on Bangle.js go to the launcher when you press the button, but in this case you're overriding it to go to the menu?

Once this clock is installed, I'm not entirely sure how you're supposed to launch any other apps?

If you really need the button to show a menu and not immediately go to the launcher, maybe you could add a menu item that called Bangle.showLauncher() (https://www.espruino.com/Reference#l_Bangle_showLauncher) so that users can launch other apps while using your clock?

@spycat111
Copy link
Contributor Author

This looks good (apart from needing the icon I mentioned) - I'd be a bit concerned that pretty much all clocks on Bangle.js go to the launcher when you press the button, but in this case you're overriding it to go to the menu?

Once this clock is installed, I'm not entirely sure how you're supposed to launch any other apps?

If you really need the button to show a menu and not immediately go to the launcher, maybe you could add a menu item that called Bangle.showLauncher() (https://www.espruino.com/Reference#l_Bangle_showLauncher) so that users can launch other apps while using your clock?

Reply:

Thanks for the feedback!

Regarding the icon — I added a 128x128px icon, but it seems that might not be sufficient. Could you clarify what format or size is expected so I can adjust it accordingly?

As for the BTN1 override: the reason I routed it to a custom menu instead of the launcher is due to the app’s complexity. It uses a significant portion of the watch’s resources, so it's designed to function as a standalone environment — potentially the only app installed on the watch. That’s why I prioritized keeping the menu accessible within the app context itself.

That said, I understand the concern. I can still add a “Go to Launcher” option in the menu using Bangle.showLauncher() for flexibility, just in case users do have other apps installed.

@spycat111
Copy link
Contributor Author

Hi - you're right - the metadata doesn't have a storage field. Looks like you added it (which was great) but then took it out though?

The metadata in general looks like a very different format to what's expected though - see https://github.com/espruino/BangleApps?tab=readme-ov-file#metadatajson-format

Or the example metadata for a clock is a good start: https://github.com/espruino/BangleApps/blob/111d80a8dea48fd5b9fcd39fe9f53e9a123e1062/apps/_example_clock/metadata.json

So you have author/src/patent/changelog fields that you don't need and which might be causing problems - and the icon needs converting to JS. I've just pushed some changes to the metadata itself. But you'll have to add app-icon.js based on https://github.com/espruino/BangleApps?tab=readme-ov-file#adding-your-app-to-the-menu (copy your existing icon to 48px wide and then convert with that tool linked)

Out of interest, how did you come up with the contents of the metadata file? Was it AI? It seems it wasn't based on any existing apps...

Thanks for catching that. I actually generated the metadata with AI, which explains why it drifted from the expected format. I’ll re-add the storage field, remove the extra fields (author, src, patent, changelog), and convert the icon to a 48×48 PNG before running it through bin/convert-icon.js to produce app-icon.js. I’ll align the JSON structure with the _example_clock/metadata.json example and push the update.

@gfwilliams
Copy link
Member

Ahh - yes, I thought it might be AI. Increasingly I'm having stuff contributed written using it, and it's a complete pain to try and debug.

I’ll re-add the storage field, remove the extra fields

I already did that for you - so I think the JSON is all good.

and convert the icon to a 48×48 PNG before running it through bin/convert-icon.js

Please just run the 48px icon through http://www.espruino.com/Image+Converter like that link says and I think everything should be fine.

@spycat111
Copy link
Contributor Author

Ahh - yes, I thought it might be AI. Increasingly I'm having stuff contributed written using it, and it's a complete pain to try and debug.

I’ll re-add the storage field, remove the extra fields

I already did that for you - so I think the JSON is all good.

and convert the icon to a 48×48 PNG before running it through bin/convert-icon.js

Please just run the 48px icon through http://www.espruino.com/Image+Converter as that link says, and I think everything should be fine.

Thanks for sorting the JSON. I’ll export the icon as a 48×48 PNG, run it through the Espruino Image Converter, and commit the resulting app-icon.js.

@spycat111
Copy link
Contributor Author

spycat111 commented Jun 12, 2025

Ahh - yes, I thought it might be AI. Increasingly I'm having stuff contributed written using it, and it's a complete pain to try and debug.

I’ll re-add the storage field, remove the extra fields

I already did that for you - so I think the JSON is all good.

and convert the icon to a 48×48 PNG before running it through bin/convert-icon.js

Just run the 48px icon through http://www.espruino.com/Image+Converter like that link says, and I think everything should be fine.

I just created app-icon.js and uploaded icon.png 48x48px.

@gfwilliams
Copy link
Member

Thanks! Looks great - it just passed the tests! So I guess it's just adding the menu item for the launcher?

... due to the app’s complexity. It uses a significant portion of the watch’s resources, so it's designed to function as a standalone environment

Actually the way Bangle.js works, adding extra apps won't actually reduce the memory available - when you run your app the device is 'yours' - if you go to the launcher or another app (unless your app explicitly says it's possible) Bangle.js tears everything down and restarts a fresh environment with that new app - so you shouldn't have to worry about making your app the only one on the device.

Only slight exception to that is widgets (which you do use) so if more widgets are added then obviously they will use more memory as they're extra code that runs.

Looking at your code if you do care about memory usage there's probably quite a lot you could do to help (like storing all your history data in Typed Arrays which are way more compact) - seems like right now you store time and the data value for each, but maybe if you know you're storing data every 10 minutes you don't need time? You can just keep it in an array and you know the 10th item is from 10*10 minutes ago?

@spycat111
Copy link
Contributor Author

spycat111 commented Jun 12, 2025

Thanks! Looks great - it just passed the tests! So I guess it's just adding the menu item for the launcher?

... due to the app’s complexity. It uses a significant portion of the watch’s resources, so it's designed to function as a standalone environment

Actually the way Bangle.js works, adding extra apps won't actually reduce the memory available - when you run your app the device is 'yours' - if you go to the launcher or another app (unless your app explicitly says it's possible) Bangle.js tears everything down and restarts a fresh environment with that new app - so you shouldn't have to worry about making your app the only one on the device.

Only slight exception to that is widgets (which you do use) so if more widgets are added then obviously they will use more memory as they're extra code that runs.

Looking at your code if you do care about memory usage there's probably quite a lot you could do to help (like storing all your history data in Typed Arrays which are way more compact) - seems like right now you store time and the data value for each, but maybe if you know you're storing data every 10 minutes you don't need time? You can just keep it in an array and you know the 10th item is from 10*10 minutes ago?

Thank you for the guidance. Here’s a concise summary and my planned update:

What the code does now

Storage & History: Buffers step, HR, light and sleep-start data in JSON files, batching writes every 5 min.

CRS & Bio‐Time: Calculates a 0–100 circadian score from stability metrics and displays bio‐time by applying the user’s phase offset.

Sensors & Alerts: Hooks into step, HRM, light sampling and sleep/hydration logic to log data and show alerts.

Rendering: Fully custom draw routine (clock:false) that paints bio‐time, CRS, steps, mood and light hours, plus a footer hint.

Menu/UI: BTN1/tap/swipe opens menuOpts, which currently includes Stats, Trend, Sleep Window, Hydration, Calibration, Export, Theme, About, Reset and Exit.

Proposed change
I would like to leave all of the above core logic intact and simply add one new touch‐menu entry:

"Go to Launcher": () => Bangle.showLauncher(),

placed alongside the existing items in menuOpts. This ensures users can return to the standard app launcher without any other modifications.

@spycat111
Copy link
Contributor Author

I've added a new touch-menu entry, "Go to Launcher"

@gfwilliams
Copy link
Member

Thanks!

@thyttan how do you feel about this? I'd say it's good to merge now?

@thyttan
Copy link
Collaborator

thyttan commented Jun 13, 2025

Please go ahead 🙂👍

@gfwilliams gfwilliams merged commit 8240c2a into espruino:master Jun 13, 2025
1 check passed
@pavelmachek
Copy link
Contributor

Just to verify... there's royalty-free, worldwide patent grant, so this is still free software?

@spycat111
Copy link
Contributor Author

Thanks for checking. As stated in the README, the code is released under the MIT license unless otherwise noted. However, please be aware that the underlying method is part of a UK patent application (GB2509149.7), and the use of the patented process may require permission or licensing. If you're planning to build on or distribute it beyond personal or experimental use, feel free to reach out.

@spycat111
Copy link
Contributor Author

spycat111 commented Jun 14, 2025

After installing the app via the development app loader I ran into a couple of show-stoppers: neither the BT calibration menu nor the sleep-window settings will actually apply, and instead I see garbled characters or odd behavior when I try. Interestingly, when uploading the same script directly via the Web IDE these issues do not occur, which further suggests the problem lies in the loader/minifier/firmware pipeline.

On digging into the generated crsclock.app.js, it looks like non-ASCII punctuation in the source is being mangled by the Espruino minifier/firmware:

Unicode minus sign (U+2212) in the BT calibration menu (around lines 612–614 in crsclock.js) causes the minifier to emit a malformed regex, leading to:

javascript
Copy
Edit
Uncaught SyntaxError: Got UNFINISHED REGEX expected ']'
EN DASH (U+2013) in the sleep-window prompt strings (lines 84–86) and in the range prompts (lines 714 and 723) renders on-device as â (or similar mojibake).

I suspect that mixing these Unicode dashes with plain ASCII hyphens is what’s breaking both parsing and display. I’ll follow up to replace all instances of EN DASH and MINUS SIGN with the standard ASCII hyphen-minus (-) so that the minifier and Bangle.js firmware handle them correctly.

Let me know if there are any objections or additional edge cases I should watch out for!


References:

Sleep window string using EN DASH (lines 84–86)

Unicode minus sign in BT calibration menu (lines 612–614)

Prompts using EN DASH for ranges (lines 714 & 723)

@pavelmachek
Copy link
Contributor

I don't believe we should have patents limiting software use. People expect to download BangleApps and use them according to the license. I believe this should be dropped from the repository.

@spycat111
Copy link
Contributor Author

spycat111 commented Jun 14, 2025

I don't believe we should have patents limiting software use. People expect to download BangleApps and use them according to the license. I believe this should be dropped from the repository.

Thanks for your message — I understand your position, but I want to be clear about mine as well.

The app is released under the MIT license and is free to use. It also implements a method described in a UK patent application. This doesn’t impose any royalty or restriction for non-commercial or personal use, especially within the open-source context of Bangle.js.

This is a prototype of a larger system I’ve been developing over the past few months, and the patent exists to protect that work — not to restrict open use or experimentation.

I believe users have the right to be informed if patented methods are used, just as I have the right to share my work under clear terms.

I hope that clarifies my position.

@gfwilliams
Copy link
Member

Thanks for spotting the issues - I'm surprised you didn't encounter the problems in the IDE as it uses the same pipeline, but maybe pretokenisation was disabled in the IDE. I'll see if I can get a fix in for the tools/IDE today although obviously for something like a hyphen non-unicode is preferable anyway.

Ideally when you submit an app you get it working in your own fork of the app loader first (info here) and then issues like this are less likely to get happen (and the CI tests help you catch issues) - but I know this is your first submission.

On the Patent front, I think we're ok (if it weren't MIT I think we have an issue). Obviously I'd prefer anything posted here to be free in every sense of the word, but at the same time it's good to be able to try out new things and also support research/new companies. My concern would be that it would open us to legal action from the patent holder but as being as you're the patent holder and are submitting it, I think that's unlikely!

I should add a warning though: There's no click-through license (nor would I want an app that needed one) when someone installs an app there's no guarantee they read that bit in your README about non-commercial and personal use - so policing the use of it would be entirely up to you (if that's a problem then it might be best not to have this app listed).

... also the MIT license does explicitly allow commercial use of the software, so there may be some conflict there. But in a way that's not our problem - if you're fine with that @spycat111 then I'm ok with it too.

@spycat111
Copy link
Contributor Author

Thanks for spotting the issues - I'm surprised you didn't encounter the problems in the IDE as it uses the same pipeline, but maybe pretokenisation was disabled in the IDE. I'll see if I can get a fix in for the tools/IDE today although obviously for something like a hyphen non-unicode is preferable anyway.

Ideally when you submit an app you get it working in your own fork of the app loader first (info here) and then issues like this are less likely to get happen (and the CI tests help you catch issues) - but I know this is your first submission.

On the Patent front, I think we're ok (if it weren't MIT I think we have an issue). Obviously I'd prefer anything posted here to be free in every sense of the word, but at the same time it's good to be able to try out new things and also support research/new companies. My concern would be that it would open us to legal action from the patent holder but as being as you're the patent holder and are submitting it, I think that's unlikely!

I should add a warning though: There's no click-through license (nor would I want an app that needed one) when someone installs an app there's no guarantee they read that bit in your README about non-commercial and personal use - so policing the use of it would be entirely up to you (if that's a problem then it might be best not to have this app listed).

... also the MIT license does explicitly allow commercial use of the software, so there may be some conflict there. But in a way that's not our problem - if you're fine with that @spycat111 then I'm ok with it too.

Thanks for the thorough response!

Just to clarify, I’m not trying to impose any restriction—I’d simply be interested to know how the app is being used in the real world, purely out of curiosity, and for future development insight. It’s not about policing or enforcing anything. But if people don’t read that part of the README, that’s totally fine, too.

Appreciate your openness on the patent topic and the balance you’re striking for the ecosystem here. And thanks again for your help with the character issue and the app loader advice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants