From 5fdd73d5e69bd0adf24d5599b0686bc47290b211 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:50:16 -0800 Subject: [PATCH 1/8] fw/services/normal/vibes: add new hourly and disconnect vibe clients Signed-off-by: Claudio Rojas --- src/fw/services/normal/vibes/vibe_client.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fw/services/normal/vibes/vibe_client.h b/src/fw/services/normal/vibes/vibe_client.h index 2f6cabf93..0a46c1e61 100644 --- a/src/fw/services/normal/vibes/vibe_client.h +++ b/src/fw/services/normal/vibes/vibe_client.h @@ -9,7 +9,9 @@ typedef enum VibeClient { VibeClient_Notifications = 0, VibeClient_PhoneCalls, VibeClient_Alarms, - VibeClient_AlarmsLPM + VibeClient_AlarmsLPM, + VibeClient_Hourly, + VibeClient_OnDisconnect, } VibeClient; // Returns the appropriate vibe score for the client. From 7a46002eb9d9a9f215fecec08e3ea05739691849 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:52:09 -0800 Subject: [PATCH 2/8] fw/services/normal/vibes: set defaults for hourly and disconnect clients Signed-off-by: Claudio Rojas --- src/fw/services/normal/vibes/vibe_score_info.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/fw/services/normal/vibes/vibe_score_info.h b/src/fw/services/normal/vibes/vibe_score_info.h index f67870dc6..06c2ad865 100644 --- a/src/fw/services/normal/vibes/vibe_score_info.h +++ b/src/fw/services/normal/vibes/vibe_score_info.h @@ -20,14 +20,20 @@ typedef enum VibeScoreId { #define DEFAULT_VIBE_SCORE_NOTIFS (VibeScoreId_Pulse) #define DEFAULT_VIBE_SCORE_INCOMING_CALLS (VibeScoreId_Pulse) #define DEFAULT_VIBE_SCORE_ALARMS (VibeScoreId_Pulse) +#define DEFAULT_VIBE_SCORE_HOURLY (VibeScoreId_Disabled) +#define DEFAULT_VIBE_SCORE_ON_DISCONNECT (VibeScoreId_Disabled) #elif PLATFORM_ASTERIX #define DEFAULT_VIBE_SCORE_NOTIFS (VibeScoreId_StandardShortPulseHigh) #define DEFAULT_VIBE_SCORE_INCOMING_CALLS (VibeScoreId_Pulse) #define DEFAULT_VIBE_SCORE_ALARMS (VibeScoreId_Reveille) +#define DEFAULT_VIBE_SCORE_HOURLY (VibeScoreId_Disabled) +#define DEFAULT_VIBE_SCORE_ON_DISCONNECT (VibeScoreId_Disabled) #else #define DEFAULT_VIBE_SCORE_NOTIFS (VibeScoreId_NudgeNudge) #define DEFAULT_VIBE_SCORE_INCOMING_CALLS (VibeScoreId_Pulse) #define DEFAULT_VIBE_SCORE_ALARMS (VibeScoreId_Reveille) +#define DEFAULT_VIBE_SCORE_HOURLY (VibeScoreId_Disabled) +#define DEFAULT_VIBE_SCORE_ON_DISCONNECT (VibeScoreId_Disabled) #endif // Returns the ResourceId for the VibeScore represented by this id. From 0f16530e1640dbaccfae1f63209718091484414e Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:53:24 -0800 Subject: [PATCH 3/8] fw/services/normal/vibes: new alert types for hourly and disconnect clients Signed-off-by: Claudio Rojas --- src/fw/services/normal/vibes/vibe_score_info.c | 12 +++++++++++- src/fw/services/normal/vibes/vibes.def | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/fw/services/normal/vibes/vibe_score_info.c b/src/fw/services/normal/vibes/vibe_score_info.c index 9292e1241..6ab61dd15 100644 --- a/src/fw/services/normal/vibes/vibe_score_info.c +++ b/src/fw/services/normal/vibes/vibe_score_info.c @@ -13,7 +13,9 @@ typedef enum AlertType { AlertType_Calls = 1 << 1, AlertType_Alarms = 1 << 2, AlertType_AlarmsLPM = 1 << 3, - AlertType_All = AlertType_Notifications | AlertType_Calls | AlertType_Alarms, + AlertType_Hourly = 1 << 4, + AlertType_OnDisconnect = 1 << 5, + AlertType_All = AlertType_Notifications | AlertType_Calls | AlertType_Alarms | AlertType_Hourly | AlertType_OnDisconnect, } AlertType; typedef struct { @@ -79,6 +81,14 @@ VibeScoreId vibe_score_info_cycle_next(VibeClient client, VibeScoreId curr_id) { alert_type = AlertType_Alarms; break; } + case VibeClient_Hourly: { + alert_type = AlertType_Hourly; + break; + } + case VibeClient_OnDisconnect: { + alert_type = AlertType_OnDisconnect; + break; + } default: { WTF; } diff --git a/src/fw/services/normal/vibes/vibes.def b/src/fw/services/normal/vibes/vibes.def index b10232368..bae349317 100644 --- a/src/fw/services/normal/vibes/vibes.def +++ b/src/fw/services/normal/vibes/vibes.def @@ -1,10 +1,10 @@ // IDs must be monotonically increasing, i.e. they must never be reused // ID 0 is reserved for "Invalid". Do not use! -VIBE_DEF(1, Disabled, i18n_ctx_noop("NotifVibe", "Disabled"), (AlertType_Notifications | AlertType_Calls), RESOURCE_ID_INVALID) -VIBE_DEF(2, StandardShortPulseLow, i18n_noop("Standard - Low"), AlertType_Notifications, RESOURCE_ID_VIBE_SCORE_STANDARD_SHORT_LOW) +VIBE_DEF(1, Disabled, i18n_ctx_noop("NotifVibe", "Disabled"), (AlertType_Notifications | AlertType_Calls | AlertType_Hourly| AlertType_OnDisconnect), RESOURCE_ID_INVALID) +VIBE_DEF(2, StandardShortPulseLow, i18n_noop("Standard - Low"), (AlertType_Notifications | AlertType_Hourly | AlertType_OnDisconnect), RESOURCE_ID_VIBE_SCORE_STANDARD_SHORT_LOW) VIBE_DEF(3, StandardLongPulseLow, i18n_noop("Standard - Low"), (AlertType_Calls | AlertType_Alarms), RESOURCE_ID_VIBE_SCORE_STANDARD_LONG_LOW) -VIBE_DEF(4, StandardShortPulseHigh, i18n_noop("Standard - High"), AlertType_Notifications, RESOURCE_ID_VIBE_SCORE_STANDARD_SHORT_HIGH) +VIBE_DEF(4, StandardShortPulseHigh, i18n_noop("Standard - High"), (AlertType_Notifications | AlertType_Hourly | AlertType_OnDisconnect), RESOURCE_ID_VIBE_SCORE_STANDARD_SHORT_HIGH) VIBE_DEF(5, StandardLongPulseHigh, i18n_noop("Standard - High"), (AlertType_Calls | AlertType_Alarms), RESOURCE_ID_VIBE_SCORE_STANDARD_LONG_HIGH) VIBE_DEF(8, Pulse, i18n_noop("Pulse"), AlertType_All, RESOURCE_ID_VIBE_SCORE_PULSE) VIBE_DEF(9, NudgeNudge, i18n_noop("Nudge Nudge"), AlertType_All, RESOURCE_ID_VIBE_SCORE_NUDGE_NUDGE) From 7ab8aa37963749eb822a99ee9cf0b6d5e89602c8 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:55:45 -0800 Subject: [PATCH 4/8] fw/services/normal/notifications: add pref storage for hourly and disconnect vibes Signed-off-by: Claudio Rojas --- .../normal/notifications/alerts_preferences.c | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/fw/services/normal/notifications/alerts_preferences.c b/src/fw/services/normal/notifications/alerts_preferences.c index 9dc5e8038..2a8af478d 100644 --- a/src/fw/services/normal/notifications/alerts_preferences.c +++ b/src/fw/services/normal/notifications/alerts_preferences.c @@ -49,6 +49,12 @@ static VibeScoreId s_vibe_score_incoming_calls = DEFAULT_VIBE_SCORE_INCOMING_CAL #define PREF_KEY_VIBE_SCORE_ALARMS ("vibeScoreAlarms") static VibeScoreId s_vibe_score_alarms = DEFAULT_VIBE_SCORE_ALARMS; + +#define PREF_KEY_VIBE_SCORE_HOURLY ("vibeScoreHourly") +static VibeScoreId s_vibe_score_hourly = DEFAULT_VIBE_SCORE_HOURLY; + +#define PREF_KEY_VIBE_SCORE_ON_DISCONNECT ("vibeScoreOnDisconnect") +static VibeScoreId s_vibe_score_on_disconnect = DEFAULT_VIBE_SCORE_ON_DISCONNECT; #endif #define PREF_KEY_DND_MANUALLY_ENABLED "dndManuallyEnabled" @@ -182,6 +188,8 @@ static void prv_save_all_vibe_scores_to_file(SettingsFile *file) { SET_PREF_ALREADY_OPEN(PREF_KEY_VIBE_SCORE_NOTIFICATIONS, s_vibe_score_notifications); SET_PREF_ALREADY_OPEN(PREF_KEY_VIBE_SCORE_INCOMING_CALLS, s_vibe_score_incoming_calls); SET_PREF_ALREADY_OPEN(PREF_KEY_VIBE_SCORE_ALARMS, s_vibe_score_alarms); + SET_PREF_ALREADY_OPEN(PREF_KEY_VIBE_SCORE_HOURLY, s_vibe_score_hourly); + SET_PREF_ALREADY_OPEN(PREF_KEY_VIBE_SCORE_ON_DISCONNECT, s_vibe_score_on_disconnect); #undef SET_PREF_ALREADY_OPEN } @@ -197,6 +205,10 @@ static void prv_ensure_valid_vibe_scores(void) { DEFAULT_VIBE_SCORE_INCOMING_CALLS); s_vibe_score_alarms = prv_return_default_if_invalid(s_vibe_score_alarms, DEFAULT_VIBE_SCORE_ALARMS); + s_vibe_score_hourly = prv_return_default_if_invalid(s_vibe_score_hourly, + DEFAULT_VIBE_SCORE_HOURLY); + s_vibe_score_on_disconnect = prv_return_default_if_invalid(s_vibe_score_on_disconnect, + DEFAULT_VIBE_SCORE_ON_DISCONNECT); } static void prv_set_vibe_scores_based_on_legacy_intensity(VibeIntensity intensity) { @@ -268,6 +280,8 @@ void alerts_preferences_init(void) { RESTORE_PREF(PREF_KEY_VIBE_SCORE_NOTIFICATIONS, s_vibe_score_notifications); RESTORE_PREF(PREF_KEY_VIBE_SCORE_INCOMING_CALLS, s_vibe_score_incoming_calls); RESTORE_PREF(PREF_KEY_VIBE_SCORE_ALARMS, s_vibe_score_alarms); + RESTORE_PREF(PREF_KEY_VIBE_SCORE_HOURLY, s_vibe_score_hourly); + RESTORE_PREF(PREF_KEY_VIBE_SCORE_ON_DISCONNECT, s_vibe_score_on_disconnect); #endif RESTORE_PREF(PREF_KEY_DND_MANUALLY_ENABLED, s_do_not_disturb_manually_enabled); RESTORE_PREF(PREF_KEY_DND_SMART_ENABLED, s_do_not_disturb_smart_dnd_enabled); @@ -401,6 +415,10 @@ VibeScoreId alerts_preferences_get_vibe_score_for_client(VibeClient client) { return s_vibe_score_incoming_calls; case VibeClient_Alarms: return s_vibe_score_alarms; + case VibeClient_Hourly: + return s_vibe_score_hourly; + case VibeClient_OnDisconnect: + return s_vibe_score_on_disconnect; default: WTF; } @@ -424,6 +442,16 @@ void alerts_preferences_set_vibe_score_for_client(VibeClient client, VibeScoreId key = PREF_KEY_VIBE_SCORE_ALARMS; break; } + case VibeClient_Hourly: { + s_vibe_score_hourly = id; + key = PREF_KEY_VIBE_SCORE_HOURLY; + break; + } + case VibeClient_OnDisconnect: { + s_vibe_score_on_disconnect = id; + key = PREF_KEY_VIBE_SCORE_ON_DISCONNECT; + break; + } default: { WTF; } From 7e770fefe0d7cfbe8c00806e6b5021fa4f93b052 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:56:46 -0800 Subject: [PATCH 5/8] fw/services/common/analytics: add analytics for new vibe triggers Signed-off-by: Claudio Rojas --- src/fw/services/common/analytics/analytics_event.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fw/services/common/analytics/analytics_event.h b/src/fw/services/common/analytics/analytics_event.h index 514ef2d71..1a6e30bd1 100644 --- a/src/fw/services/common/analytics/analytics_event.h +++ b/src/fw/services/common/analytics/analytics_event.h @@ -240,6 +240,8 @@ typedef enum VibePatternFeature { VibePatternFeature_Notifications = 1 << 0, VibePatternFeature_PhoneCalls = 1 << 1, VibePatternFeature_Alarms = 1 << 2, + VibePatternFeature_Hourly = 1 << 3, + VibePatternFeature_OnDisconnect = 1 << 4, } VibePatternFeature; typedef struct PACKED { From b7ecfe3093952a83e9cbb6f9a13bd3a5de9bd5c9 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:58:11 -0800 Subject: [PATCH 6/8] fw/services/common: add vibe trigger for the top of the hour Signed-off-by: Claudio Rojas --- src/fw/services/common/clock.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/fw/services/common/clock.c b/src/fw/services/common/clock.c index 50d42b039..b50f391d7 100644 --- a/src/fw/services/common/clock.c +++ b/src/fw/services/common/clock.c @@ -22,6 +22,15 @@ #include "util/net.h" #include "util/size.h" #include "util/string.h" +#ifndef RECOVERY_FW +#include "services/normal/notifications/do_not_disturb.h" +#include "services/normal/notifications/alerts.h" +#include "services/normal/notifications/alerts_preferences_private.h" +#if CAPABILITY_HAS_VIBE_SCORES +#include "services/normal/vibes/vibe_client.h" +#include "services/normal/vibes/vibe_score.h" +#endif +#endif #include @@ -387,6 +396,23 @@ void clock_protocol_msg_callback(CommSession *session, const uint8_t* data, unsi static void prv_watch_dst(void* user) { const bool was_dst = (bool)user; const bool is_dst = time_get_isdst(rtc_get_time()); + +#ifndef RECOVERY_FW + if (alerts_should_vibrate_for_type(AlertOther)) { +#if CAPABILITY_HAS_VIBE_SCORES + if (time_utc_to_local(rtc_get_time()) % 3600 == 0) { + uint32_t vibe_id = vibe_score_info_get_resource_id( + alerts_preferences_get_vibe_score_for_client(VibeClient_Hourly)); + VibeScore *score = vibe_score_create_with_resource_system(0, vibe_id); + if (score) { + vibe_score_do_vibe(score); + vibe_score_destroy(score); + } + } +#endif + } +#endif + if (is_dst != was_dst) { PebbleEvent e = { .type = PEBBLE_SET_TIME_EVENT, From 6eb368caa367c508cf1ac9ffa60a65b6ea924d26 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:58:32 -0800 Subject: [PATCH 7/8] fw/services/common: add vibe trigger for debounced disconnect Signed-off-by: Claudio Rojas --- .../common/debounced_connection_service.c | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/fw/services/common/debounced_connection_service.c b/src/fw/services/common/debounced_connection_service.c index 101026b88..476e825a2 100644 --- a/src/fw/services/common/debounced_connection_service.c +++ b/src/fw/services/common/debounced_connection_service.c @@ -7,6 +7,18 @@ #include "services/common/regular_timer.h" #include "syscall/syscall_internal.h" +#ifndef RECOVERY_FW +#include "services/normal/notifications/do_not_disturb.h" +#include "services/normal/notifications/alerts.h" +#include "services/normal/notifications/alerts_preferences_private.h" +#if CAPABILITY_HAS_VIBE_SCORES +#include "services/normal/vibes/vibe_client.h" +#include "services/normal/vibes/vibe_score.h" +#endif +#endif + +#include "system/logging.h" + //! This module is responsible for propagating debounced connection events. //! Connection events are passed through right away to subscribers but //! disconnection events are only passed through if a re-connection did not @@ -40,6 +52,19 @@ static void prv_handle_disconnection_debounced(void *data) { DebounceConnection conn_id = (DebounceConnection)data; s_debounced_state_is_connected[conn_id] = false; prv_put_debounced_connection_event(conn_id); +#ifndef RECOVERY_FW + if (alerts_should_vibrate_for_type(AlertOther)) { +#if CAPABILITY_HAS_VIBE_SCORES + uint32_t vibe_id = vibe_score_info_get_resource_id( + alerts_preferences_get_vibe_score_for_client(VibeClient_OnDisconnect)); + VibeScore *score = vibe_score_create_with_resource_system(0, vibe_id); + if (score) { + vibe_score_do_vibe(score); + vibe_score_destroy(score); + } +#endif + } +#endif regular_timer_remove_callback(&s_debounce_timers[conn_id]); } From 207de8534e1d2f80d1b744ec5315d80353b47240 Mon Sep 17 00:00:00 2001 From: Claudio <71095411+rojas-claudio@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:59:49 -0800 Subject: [PATCH 8/8] settings: add hourly and disconnect configurations to vibe menu Signed-off-by: Claudio Rojas --- .../settings/settings_vibe_patterns.c | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/fw/apps/system_apps/settings/settings_vibe_patterns.c b/src/fw/apps/system_apps/settings/settings_vibe_patterns.c index 86d744893..50159b53e 100644 --- a/src/fw/apps/system_apps/settings/settings_vibe_patterns.c +++ b/src/fw/apps/system_apps/settings/settings_vibe_patterns.c @@ -23,6 +23,8 @@ typedef enum VibeSettingsRow { VibeSettingsRow_Notifications = 0, VibeSettingsRow_PhoneCalls, VibeSettingsRow_Alarms, + VibeSettingsRow_Hourly, + VibeSettingsRow_OnDisconnect, VibeSettingsRow_System, VibeSettingsRow_Count, } VibeSettingsRow; @@ -72,6 +74,16 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx, client = VibeClient_Alarms; break; } + case VibeSettingsRow_Hourly: { + title = i18n_noop("Hourly Notice"); + client = VibeClient_Hourly; + break; + } + case VibeSettingsRow_OnDisconnect: { + title = i18n_noop("On Disconnect"); + client = VibeClient_OnDisconnect; + break; + } case VibeSettingsRow_System: { /// Refers to the class of all non-score vibes, e.g. 3rd party app vibes title = i18n_noop("System"); @@ -112,6 +124,14 @@ static void prv_selection_changed_cb(SettingsCallbacks *context, uint16_t new_ro score = vibe_client_get_score(VibeClient_Alarms); break; } + case VibeSettingsRow_Hourly: { + score = vibe_client_get_score(VibeClient_Hourly); + break; + } + case VibeSettingsRow_OnDisconnect: { + score = vibe_client_get_score(VibeClient_OnDisconnect); + break; + } case VibeSettingsRow_System: { // Vibe a short pulse so the user can feel the current system default vibe intensity vibes_short_pulse(); @@ -150,6 +170,16 @@ static void prv_select_click_cb(SettingsCallbacks *context, uint16_t row) { client = VibeClient_Alarms; break; } + case VibeSettingsRow_Hourly: { + data->toggled_vibes_mask |= VibePatternFeature_Hourly; + client = VibeClient_Hourly; + break; + } + case VibeSettingsRow_OnDisconnect: { + data->toggled_vibes_mask |= VibePatternFeature_OnDisconnect; + client = VibeClient_OnDisconnect; + break; + } case VibeSettingsRow_System: { const VibeIntensity current_system_default_vibe_intensity = vibe_intensity_get(); const VibeIntensity next_system_default_vibe_intensity =