1
+ #include <furi.h>
2
+ #include <furi_hal.h>
3
+
4
+ #include <gui/gui.h>
5
+ #include <gui/elements.h>
6
+
7
+ #include <notification/notification_messages.h>
8
+ #include <notification/notification_app.h>
9
+
10
+ #include "clock_app.h"
11
+
12
+ /*
13
+ This is a modified version of the default clock app intended for use overnight
14
+ Up / Down control the displays brightness. Down at brightness 0 turns the notification LED on and off.
15
+ */
16
+
17
+ int brightness = 5 ;
18
+ bool led = false;
19
+ NotificationApp * notif = 0 ;
20
+
21
+ const NotificationMessage message_red_dim = {
22
+ .type = NotificationMessageTypeLedRed ,
23
+ .data .led .value = 0xFF / 16 ,
24
+ };
25
+
26
+ const NotificationMessage message_red_off = {
27
+ .type = NotificationMessageTypeLedRed ,
28
+ .data .led .value = 0x00 ,
29
+ };
30
+
31
+ static const NotificationSequence led_on = {
32
+ & message_red_dim ,
33
+ & message_do_not_reset ,
34
+ NULL ,
35
+ };
36
+
37
+ static const NotificationSequence led_off = {
38
+ & message_red_off ,
39
+ & message_do_not_reset ,
40
+ NULL ,
41
+ };
42
+
43
+ static const NotificationSequence led_reset = {
44
+ & message_red_0 ,
45
+ NULL ,
46
+ };
47
+
48
+ void set_backlight_brightness (float brightness ){
49
+ notif -> settings .display_brightness = brightness ;
50
+ notification_message (notif , & sequence_display_backlight_on );
51
+ }
52
+
53
+ void handle_up (){
54
+ if (brightness < 100 ){
55
+ led = false;
56
+ notification_message (notif , & led_off );
57
+ brightness += 5 ;
58
+ }
59
+ set_backlight_brightness ((float )(brightness / 100.f ));
60
+ }
61
+
62
+ void handle_down (){
63
+ if (brightness > 0 ){
64
+ brightness -= 5 ;
65
+ if (brightness == 0 ){ //trigger only on the first brightness 5 -> 0 transition
66
+ led = true;
67
+ notification_message (notif , & led_on );
68
+ }
69
+ }
70
+ else if (brightness == 0 ){ //trigger on every down press afterwards
71
+ led = !led ;
72
+ if (led ){ notification_message (notif , & led_on ); }
73
+ else { notification_message (notif , & led_off ); }
74
+ }
75
+ set_backlight_brightness ((float )(brightness / 100.f ));
76
+ }
77
+
78
+ static void clock_input_callback (InputEvent * input_event , FuriMessageQueue * event_queue ) {
79
+ furi_assert (event_queue );
80
+ PluginEvent event = {.type = EventTypeKey , .input = * input_event };
81
+ furi_message_queue_put (event_queue , & event , FuriWaitForever );
82
+ }
83
+
84
+ static void clock_render_callback (Canvas * const canvas , void * ctx ) {
85
+ //canvas_clear(canvas);
86
+ //canvas_set_color(canvas, ColorBlack);
87
+
88
+ //avoids a bug with the brightness being reverted after the backlight-off period
89
+ set_backlight_brightness ((float )(brightness / 100.f ));
90
+
91
+ ClockState * state = ctx ;
92
+ if (furi_mutex_acquire (state -> mutex , 200 ) != FuriStatusOk ) {
93
+ //FURI_LOG_D(TAG, "Can't obtain mutex, requeue render");
94
+ PluginEvent event = {.type = EventTypeTick };
95
+ furi_message_queue_put (state -> event_queue , & event , 0 );
96
+ return ;
97
+ }
98
+
99
+ FuriHalRtcDateTime curr_dt ;
100
+ furi_hal_rtc_get_datetime (& curr_dt );
101
+ uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp (& curr_dt );
102
+
103
+ char time_string [TIME_LEN ];
104
+ char date_string [DATE_LEN ];
105
+ char meridian_string [MERIDIAN_LEN ];
106
+ char timer_string [20 ];
107
+
108
+ if (state -> time_format == LocaleTimeFormat24h ) {
109
+ snprintf (
110
+ time_string , TIME_LEN , CLOCK_TIME_FORMAT , curr_dt .hour , curr_dt .minute , curr_dt .second );
111
+ } else {
112
+ bool pm = curr_dt .hour > 12 ;
113
+ bool pm12 = curr_dt .hour >= 12 ;
114
+ snprintf (
115
+ time_string ,
116
+ TIME_LEN ,
117
+ CLOCK_TIME_FORMAT ,
118
+ pm ? curr_dt .hour - 12 : curr_dt .hour ,
119
+ curr_dt .minute ,
120
+ curr_dt .second );
121
+
122
+ snprintf (
123
+ meridian_string ,
124
+ MERIDIAN_LEN ,
125
+ MERIDIAN_FORMAT ,
126
+ pm12 ? MERIDIAN_STRING_PM : MERIDIAN_STRING_AM );
127
+ }
128
+
129
+ if (state -> date_format == LocaleDateFormatYMD ) {
130
+ snprintf (
131
+ date_string , DATE_LEN , CLOCK_ISO_DATE_FORMAT , curr_dt .year , curr_dt .month , curr_dt .day );
132
+ } else if (state -> date_format == LocaleDateFormatMDY ) {
133
+ snprintf (
134
+ date_string , DATE_LEN , CLOCK_RFC_DATE_FORMAT , curr_dt .month , curr_dt .day , curr_dt .year );
135
+ } else {
136
+ snprintf (
137
+ date_string , DATE_LEN , CLOCK_RFC_DATE_FORMAT , curr_dt .day , curr_dt .month , curr_dt .year );
138
+ }
139
+
140
+ bool timer_running = state -> timer_running ;
141
+ uint32_t timer_start_timestamp = state -> timer_start_timestamp ;
142
+ uint32_t timer_stopped_seconds = state -> timer_stopped_seconds ;
143
+
144
+ furi_mutex_release (state -> mutex );
145
+
146
+ canvas_set_font (canvas , FontBigNumbers );
147
+
148
+ if (timer_start_timestamp != 0 ) {
149
+ int32_t elapsed_secs = timer_running ? (curr_ts - timer_start_timestamp ) :
150
+ timer_stopped_seconds ;
151
+ snprintf (timer_string , 20 , "%.2ld:%.2ld" , elapsed_secs / 60 , elapsed_secs % 60 );
152
+ canvas_draw_str_aligned (canvas , 64 , 8 , AlignCenter , AlignCenter , time_string ); // DRAW TIME
153
+ canvas_draw_str_aligned (canvas , 64 , 32 , AlignCenter , AlignTop , timer_string ); // DRAW TIMER
154
+ canvas_set_font (canvas , FontSecondary );
155
+ canvas_draw_str_aligned (canvas , 64 , 20 , AlignCenter , AlignTop , date_string ); // DRAW DATE
156
+ elements_button_left (canvas , "Reset" );
157
+ } else {
158
+ canvas_draw_str_aligned (canvas , 64 , 32 , AlignCenter , AlignCenter , time_string );
159
+ canvas_set_font (canvas , FontSecondary );
160
+ canvas_draw_str_aligned (canvas , 65 , 17 , AlignCenter , AlignCenter , date_string );
161
+
162
+ if (state -> time_format == LocaleTimeFormat12h )
163
+ canvas_draw_str_aligned (canvas , 64 , 47 , AlignCenter , AlignCenter , meridian_string );
164
+ }
165
+ if (timer_running ) {
166
+ elements_button_center (canvas , "Stop" );
167
+ } else if (timer_start_timestamp != 0 && !timer_running ) {
168
+ elements_button_center (canvas , "Start" );
169
+ }
170
+ }
171
+
172
+ static void clock_state_init (ClockState * const state ) {
173
+ state -> time_format = locale_get_time_format ();
174
+
175
+ state -> date_format = locale_get_date_format ();
176
+
177
+ //FURI_LOG_D(TAG, "Time format: %s", state->settings.time_format == H12 ? "12h" : "24h");
178
+ //FURI_LOG_D(TAG, "Date format: %s", state->settings.date_format == Iso ? "ISO 8601" : "RFC 5322");
179
+ //furi_hal_rtc_get_datetime(&state->datetime);
180
+ }
181
+
182
+ // Runs every 1000ms by default
183
+ static void clock_tick (void * ctx ) {
184
+ furi_assert (ctx );
185
+ FuriMessageQueue * event_queue = ctx ;
186
+ PluginEvent event = {.type = EventTypeTick };
187
+ // It's OK to loose this event if system overloaded
188
+ furi_message_queue_put (event_queue , & event , 0 );
189
+ }
190
+
191
+ void timer_start_stop (ClockState * plugin_state ){
192
+ // START/STOP TIMER
193
+ FuriHalRtcDateTime curr_dt ;
194
+ furi_hal_rtc_get_datetime (& curr_dt );
195
+ uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp (& curr_dt );
196
+
197
+ if (plugin_state -> timer_running ) {
198
+ // Update stopped seconds
199
+ plugin_state -> timer_stopped_seconds = curr_ts - plugin_state -> timer_start_timestamp ;
200
+ } else {
201
+ if (plugin_state -> timer_start_timestamp == 0 ) {
202
+ // Set starting timestamp if this is first time
203
+ plugin_state -> timer_start_timestamp = curr_ts ;
204
+ } else {
205
+ // Timer was already running, need to slightly readjust so we don't
206
+ // count the intervening time
207
+ plugin_state -> timer_start_timestamp = curr_ts - plugin_state -> timer_stopped_seconds ;
208
+ }
209
+ }
210
+ plugin_state -> timer_running = !plugin_state -> timer_running ;
211
+ }
212
+
213
+ void timer_reset_seconds (ClockState * plugin_state ){
214
+ if (plugin_state -> timer_start_timestamp != 0 ) {
215
+ // Reset seconds
216
+ plugin_state -> timer_running = false;
217
+ plugin_state -> timer_start_timestamp = 0 ;
218
+ plugin_state -> timer_stopped_seconds = 0 ;
219
+ }
220
+ }
221
+
222
+ int32_t clock_app (void * p ) {
223
+ UNUSED (p );
224
+ ClockState * plugin_state = malloc (sizeof (ClockState ));
225
+
226
+ plugin_state -> event_queue = furi_message_queue_alloc (8 , sizeof (PluginEvent ));
227
+ if (plugin_state -> event_queue == NULL ) {
228
+ FURI_LOG_E (TAG , "Cannot create event queue" );
229
+ free (plugin_state );
230
+ return 255 ;
231
+ }
232
+ //FURI_LOG_D(TAG, "Event queue created");
233
+
234
+ plugin_state -> mutex = furi_mutex_alloc (FuriMutexTypeNormal );
235
+ if (plugin_state -> mutex == NULL ) {
236
+ FURI_LOG_E (TAG , "Cannot create mutex" );
237
+ furi_message_queue_free (plugin_state -> event_queue );
238
+ free (plugin_state );
239
+ return 255 ;
240
+ }
241
+ //FURI_LOG_D(TAG, "Mutex created");
242
+
243
+ clock_state_init (plugin_state );
244
+
245
+ // Set system callbacks
246
+ ViewPort * view_port = view_port_alloc ();
247
+ view_port_draw_callback_set (view_port , clock_render_callback , plugin_state );
248
+ view_port_input_callback_set (view_port , clock_input_callback , plugin_state -> event_queue );
249
+
250
+ FuriTimer * timer = furi_timer_alloc (clock_tick , FuriTimerTypePeriodic , plugin_state -> event_queue );
251
+
252
+ if (timer == NULL ) {
253
+ FURI_LOG_E (TAG , "Cannot create timer" );
254
+ furi_mutex_free (plugin_state -> mutex );
255
+ furi_message_queue_free (plugin_state -> event_queue );
256
+ free (plugin_state );
257
+ return 255 ;
258
+ }
259
+ //FURI_LOG_D(TAG, "Timer created");
260
+
261
+ // Open GUI and register view_port
262
+ Gui * gui = furi_record_open (RECORD_GUI );
263
+ gui_add_view_port (gui , view_port , GuiLayerFullscreen );
264
+
265
+ furi_timer_start (timer , furi_kernel_get_tick_frequency ());
266
+ //FURI_LOG_D(TAG, "Timer started");
267
+
268
+ notif = furi_record_open (RECORD_NOTIFICATION );
269
+ float tmpBrightness = notif -> settings .display_brightness ;
270
+
271
+ notification_message (notif , & sequence_display_backlight_enforce_on );
272
+ notification_message (notif , & led_off );
273
+
274
+ // Main loop
275
+ PluginEvent event ;
276
+ for (bool processing = true; processing ;) {
277
+ FuriStatus event_status = furi_message_queue_get (plugin_state -> event_queue , & event , 100 );
278
+
279
+ if (event_status != FuriStatusOk ) continue ;
280
+
281
+ if (furi_mutex_acquire (plugin_state -> mutex , FuriWaitForever ) != FuriStatusOk ) continue ;
282
+ // press events
283
+ if (event .type == EventTypeKey ) {
284
+ if (event .input .type == InputTypeLong ) {
285
+ switch (event .input .key ) {
286
+ case InputKeyLeft :
287
+ // Reset seconds
288
+ timer_reset_seconds (plugin_state );
289
+ break ;
290
+ case InputKeyOk :
291
+ // Toggle timer
292
+ timer_start_stop (plugin_state );
293
+ break ;
294
+ case InputKeyBack :
295
+ // Exit the plugin
296
+ processing = false;
297
+ break ;
298
+ default :
299
+ break ;
300
+ }
301
+ }
302
+ else if (event .input .type == InputTypeShort ) {
303
+ switch (event .input .key ) {
304
+ case InputKeyUp :
305
+ handle_up ();
306
+ break ;
307
+ case InputKeyDown :
308
+ handle_down ();
309
+ break ;
310
+ default :
311
+ break ;
312
+ }
313
+ }
314
+ } /*else if(event.type == EventTypeTick) {
315
+ furi_hal_rtc_get_datetime(&plugin_state->datetime);
316
+ }*/
317
+
318
+ view_port_update (view_port );
319
+ furi_mutex_release (plugin_state -> mutex );
320
+ }
321
+
322
+ furi_timer_free (timer );
323
+ view_port_enabled_set (view_port , false);
324
+ gui_remove_view_port (gui , view_port );
325
+ furi_record_close (RECORD_GUI );
326
+ view_port_free (view_port );
327
+ furi_message_queue_free (plugin_state -> event_queue );
328
+ furi_mutex_free (plugin_state -> mutex );
329
+ free (plugin_state );
330
+
331
+ set_backlight_brightness (tmpBrightness );
332
+ notification_message (notif , & sequence_display_backlight_enforce_auto );
333
+ notification_message (notif , & led_reset );
334
+
335
+ return 0 ;
336
+ }
0 commit comments