commit e6560e7f271134addcbadcf3c326feec4b76b0d8
parent 3609fbfe951db5e1fac604ce4e0570bb7b7a926e
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date: Tue, 17 Feb 2026 21:13:58 +0100
refactor: simplify code structure and style
Diffstat:
14 files changed, 916 insertions(+), 980 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,6 +7,18 @@ BIN = dist/timed_remote.fap
SRC = \
application.fam \
timed_remote.c \
+ timed_remote.h \
+ helpers/ir_helper.c \
+ helpers/ir_helper.h \
+ helpers/time_helper.c \
+ helpers/time_helper.h \
+ scenes/timed_remote_scene.c \
+ scenes/timed_remote_scene.h \
+ scenes/scene_ir_browse.c \
+ scenes/scene_ir_select.c \
+ scenes/scene_timer_config.c \
+ scenes/scene_timer_running.c \
+ scenes/scene_confirm.c \
dist/timed_remote.fap: ${BUILDER} ${SRC} ${PYENV}
${BUILDER}
@@ -25,4 +37,4 @@ start: ${BIN}
clean:
rm -rf dist
-.PHONY: start clean
-\ No newline at end of file
+.PHONY: start clean
diff --git a/helpers/ir_helper.c b/helpers/ir_helper.c
@@ -6,175 +6,237 @@
#include <lib/infrared/signal/infrared_error_code.h>
#include <storage/storage.h>
-#define IR_FILE_HEADER "IR signals file"
-#define IR_FILE_VERSION 1
-
-/* ========== Signal List ========== */
+IrSignalList *
+ir_list_alloc(void)
+{
+ IrSignalList *list = malloc(sizeof(IrSignalList));
+ if (!list)
+ return NULL;
+ list->items = NULL;
+ list->count = 0;
+ list->capacity = 0;
+ return list;
+}
-IrSignalList *ir_signal_list_alloc(void) {
- IrSignalList *list = malloc(sizeof(IrSignalList));
- list->items = NULL;
- list->count = 0;
- list->capacity = 0;
- return list;
+void
+ir_list_free(IrSignalList *list)
+{
+ if (!list)
+ return;
+
+ for (size_t i = 0; i < list->count; i++)
+ {
+ if (list->items[i].signal)
+ {
+ infrared_signal_free(list->items[i].signal);
+ }
+ if (list->items[i].name)
+ {
+ furi_string_free(list->items[i].name);
+ }
+ }
+ if (list->items)
+ {
+ free(list->items);
+ }
+ free(list);
}
-void ir_signal_list_free(IrSignalList *list) {
- if (!list)
- return;
-
- for (size_t i = 0; i < list->count; i++) {
- if (list->items[i].signal) {
- infrared_signal_free(list->items[i].signal);
- }
- if (list->items[i].name) {
- furi_string_free(list->items[i].name);
- }
- }
- if (list->items) {
- free(list->items);
- }
- free(list);
+static bool
+add_sig(IrSignalList *list, InfraredSignal *signal, const char *name)
+{
+ if (list->count >= list->capacity)
+ {
+ size_t new_capacity = list->capacity == 0 ? 8 : list->capacity * 2;
+ IrSignalItem *items = realloc(list->items, new_capacity * sizeof(IrSignalItem));
+ if (!items)
+ return false;
+ list->items = items;
+ list->capacity = new_capacity;
+ }
+
+ list->items[list->count].signal = signal;
+ list->items[list->count].name = furi_string_alloc_set(name);
+ if (!list->items[list->count].name)
+ return false;
+ list->count++;
+ return true;
}
-static void ir_signal_list_add(IrSignalList *list, InfraredSignal *signal,
- const char *name) {
- if (list->count >= list->capacity) {
- size_t new_capacity = list->capacity == 0 ? 8 : list->capacity * 2;
- list->items = realloc(list->items, new_capacity * sizeof(IrSignalItem));
- list->capacity = new_capacity;
- }
-
- list->items[list->count].signal = signal;
- list->items[list->count].name = furi_string_alloc_set(name);
- list->count++;
+bool
+ir_load(const char *path, IrSignalList *list)
+{
+ if (!path || !list)
+ return false;
+
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ return false;
+
+ FlipperFormat *ff = flipper_format_file_alloc(storage);
+ if (!ff)
+ {
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ bool success = false;
+ FuriString *filetype = NULL;
+ FuriString *sig = NULL;
+
+ do
+ {
+ if (!flipper_format_file_open_existing(ff, path))
+ break;
+
+ filetype = furi_string_alloc();
+ if (!filetype)
+ break;
+
+ uint32_t version;
+ bool header_ok = flipper_format_read_header(ff, filetype, &version);
+ if (!header_ok)
+ break;
+
+ sig = furi_string_alloc();
+ if (!sig)
+ break;
+
+ while (true)
+ {
+ InfraredSignal *signal = infrared_signal_alloc();
+ if (!signal)
+ break;
+
+ InfraredErrorCode err = infrared_signal_read(signal, ff, sig);
+ if (err == InfraredErrorCodeNone)
+ {
+ if (!add_sig(list, signal, furi_string_get_cstr(sig)))
+ {
+ infrared_signal_free(signal);
+ break;
+ }
+ } else
+ {
+ infrared_signal_free(signal);
+ break;
+ }
+ }
+ success = true;
+ } while (false);
+
+ if (filetype)
+ furi_string_free(filetype);
+ if (sig)
+ furi_string_free(sig);
+ flipper_format_free(ff);
+ furi_record_close(RECORD_STORAGE);
+ return success;
}
-/* ========== File I/O ========== */
-
-bool ir_helper_load_file(const char *path, IrSignalList *list) {
- Storage *storage = furi_record_open(RECORD_STORAGE);
- FlipperFormat *ff = flipper_format_file_alloc(storage);
- bool success = false;
-
- do {
- if (!flipper_format_file_open_existing(ff, path))
- break;
-
- /* Verify header */
- FuriString *filetype = furi_string_alloc();
- uint32_t version;
- bool header_ok = flipper_format_read_header(ff, filetype, &version);
- furi_string_free(filetype);
- if (!header_ok)
- break;
-
- /* Read all signals */
- FuriString *signal_name = furi_string_alloc();
- while (true) {
- InfraredSignal *signal = infrared_signal_alloc();
- InfraredErrorCode err = infrared_signal_read(signal, ff, signal_name);
- if (err == InfraredErrorCodeNone) {
- ir_signal_list_add(list, signal, furi_string_get_cstr(signal_name));
- } else {
- infrared_signal_free(signal);
- break;
- }
- }
- furi_string_free(signal_name);
-
- success = true;
- } while (false);
-
- flipper_format_free(ff);
- furi_record_close(RECORD_STORAGE);
- return success;
+void
+ir_tx(InfraredSignal *signal)
+{
+ infrared_signal_transmit(signal);
}
-/* ========== Transmit ========== */
+static bool
+is_ir(const FileInfo *file_info, const char *name)
+{
+ if ((file_info->flags & FSF_DIRECTORY) || !name)
+ return false;
-void ir_helper_transmit(InfraredSignal *signal) {
- infrared_signal_transmit(signal);
+ size_t len = strlen(name);
+ return len > 3 && strcmp(name + len - 3, ".ir") == 0;
}
-/* ========== File Browser ========== */
-
-bool ir_helper_list_files(const char *dir_path, FuriString ***files,
- size_t *count) {
- Storage *storage = furi_record_open(RECORD_STORAGE);
- File *dir = storage_file_alloc(storage);
-
- *files = NULL;
- *count = 0;
-
- if (!storage_dir_open(dir, dir_path)) {
- storage_file_free(dir);
- furi_record_close(RECORD_STORAGE);
- return false;
- }
-
- /* First pass: count .ir files */
- FileInfo file_info;
- char name_buf[256];
- size_t file_count = 0;
-
- while (storage_dir_read(dir, &file_info, name_buf, sizeof(name_buf))) {
- if (!(file_info.flags & FSF_DIRECTORY)) {
- size_t len = strlen(name_buf);
- if (len > 3 && strcmp(name_buf + len - 3, ".ir") == 0) {
- file_count++;
- }
- }
- }
-
- if (file_count == 0) {
- storage_dir_close(dir);
- storage_file_free(dir);
- furi_record_close(RECORD_STORAGE);
- return true; /* Success, but no files */
- }
-
- /* Allocate array */
- *files = malloc(file_count * sizeof(FuriString *));
- *count = file_count;
-
- /* Second pass: collect filenames - close and reopen directory */
- storage_dir_close(dir);
- if (!storage_dir_open(dir, dir_path)) {
- free(*files);
- *files = NULL;
- *count = 0;
- storage_file_free(dir);
- furi_record_close(RECORD_STORAGE);
- return false;
- }
-
- size_t idx = 0;
-
- while (storage_dir_read(dir, &file_info, name_buf, sizeof(name_buf)) &&
- idx < file_count) {
- if (!(file_info.flags & FSF_DIRECTORY)) {
- size_t len = strlen(name_buf);
- if (len > 3 && strcmp(name_buf + len - 3, ".ir") == 0) {
- (*files)[idx] = furi_string_alloc_set(name_buf);
- idx++;
- }
- }
- }
-
- storage_dir_close(dir);
- storage_file_free(dir);
- furi_record_close(RECORD_STORAGE);
- return true;
+bool
+ir_files(const char *dir_path, FuriString ***files,
+ size_t *count)
+{
+ if (!dir_path || !files || !count)
+ return false;
+
+ *files = NULL;
+ *count = 0;
+
+ Storage *storage = furi_record_open(RECORD_STORAGE);
+ if (!storage)
+ return false;
+
+ File *dir = storage_file_alloc(storage);
+ if (!dir)
+ {
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ if (!storage_dir_open(dir, dir_path))
+ {
+ storage_file_free(dir);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+
+ FileInfo file_info;
+ char name_buf[256];
+ size_t capacity = 0;
+
+ while (storage_dir_read(dir, &file_info, name_buf, sizeof(name_buf)))
+ {
+ if (!is_ir(&file_info, name_buf))
+ continue;
+
+ if (*count >= capacity)
+ {
+ size_t new_capacity = capacity == 0 ? 8 : capacity * 2;
+ FuriString **next_files = realloc(*files, new_capacity * sizeof(FuriString *));
+ if (!next_files)
+ {
+ ir_files_free(*files, *count);
+ *files = NULL;
+ *count = 0;
+ storage_dir_close(dir);
+ storage_file_free(dir);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ *files = next_files;
+ capacity = new_capacity;
+ }
+
+ (*files)[*count] = furi_string_alloc_set(name_buf);
+ if (!(*files)[*count])
+ {
+ ir_files_free(*files, *count);
+ *files = NULL;
+ *count = 0;
+ storage_dir_close(dir);
+ storage_file_free(dir);
+ furi_record_close(RECORD_STORAGE);
+ return false;
+ }
+ (*count)++;
+ }
+
+ storage_dir_close(dir);
+ storage_file_free(dir);
+ furi_record_close(RECORD_STORAGE);
+ return true;
}
-void ir_helper_free_file_list(FuriString **files, size_t count) {
- if (!files)
- return;
- for (size_t i = 0; i < count; i++) {
- if (files[i]) {
- furi_string_free(files[i]);
- }
- }
- free(files);
+void
+ir_files_free(FuriString **files, size_t count)
+{
+ if (!files)
+ return;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ if (files[i])
+ {
+ furi_string_free(files[i]);
+ }
+ }
+ free(files);
}
diff --git a/helpers/ir_helper.h b/helpers/ir_helper.h
@@ -1,64 +1,28 @@
#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+
#include <furi.h>
#include <infrared.h>
#include <lib/infrared/signal/infrared_signal.h>
-/**
- * IR signal container with name.
- */
-typedef struct {
- InfraredSignal *signal;
- FuriString *name;
+typedef struct
+{
+ InfraredSignal *signal;
+ FuriString *name;
} IrSignalItem;
-/**
- * List of signals parsed from an .ir file.
- */
-typedef struct {
- IrSignalItem *items;
- size_t count;
- size_t capacity;
+typedef struct
+{
+ IrSignalItem *items;
+ size_t count;
+ size_t capacity;
} IrSignalList;
-/**
- * Allocate an IR signal list.
- */
-IrSignalList *ir_signal_list_alloc(void);
-
-/**
- * Free an IR signal list and all contained signals.
- */
-void ir_signal_list_free(IrSignalList *list);
-
-/**
- * Load all signals from an .ir file.
- *
- * @param path File path to load
- * @param list Output list of signals
- * @return true on success
- */
-bool ir_helper_load_file(const char *path, IrSignalList *list);
-
-/**
- * Transmit an IR signal.
- *
- * @param signal The signal to transmit
- */
-void ir_helper_transmit(InfraredSignal *signal);
-
-/**
- * List .ir files in a directory.
- *
- * @param dir_path Directory path (e.g., "/ext/infrared")
- * @param files Output: array of FuriString* (caller frees)
- * @param count Output: number of files
- * @return true on success
- */
-bool ir_helper_list_files(const char *dir_path, FuriString ***files,
- size_t *count);
-
-/**
- * Free a list of file paths.
- */
-void ir_helper_free_file_list(FuriString **files, size_t count);
+IrSignalList *ir_list_alloc(void);
+void ir_list_free(IrSignalList *list);
+bool ir_load(const char *path, IrSignalList *list);
+void ir_tx(InfraredSignal *signal);
+bool ir_files(const char *dir_path, FuriString ***files, size_t *count);
+void ir_files_free(FuriString **files, size_t count);
diff --git a/helpers/time_helper.c b/helpers/time_helper.c
@@ -5,46 +5,55 @@
#include <stdio.h>
#endif
-uint32_t time_helper_hms_to_seconds(uint8_t h, uint8_t m, uint8_t s) {
- return (uint32_t)h * 3600 + (uint32_t)m * 60 + (uint32_t)s;
+uint32_t
+time_hms_sec(uint8_t h, uint8_t m, uint8_t s)
+{
+ return (uint32_t) h * 3600 + (uint32_t) m * 60 + (uint32_t) s;
}
-void time_helper_seconds_to_hms(uint32_t total_seconds, uint8_t *h, uint8_t *m,
- uint8_t *s) {
- /* Handle times >= 24 hours by wrapping */
- total_seconds = total_seconds % (24 * 3600);
-
- *h = (uint8_t)(total_seconds / 3600);
- total_seconds %= 3600;
- *m = (uint8_t)(total_seconds / 60);
- *s = (uint8_t)(total_seconds % 60);
+void
+time_sec_hms(uint32_t total_seconds, uint8_t *h, uint8_t *m,
+ uint8_t *s)
+{
+ /* Handle times >= 24 hours by wrapping */
+ total_seconds = total_seconds % (24 * 3600);
+
+ *h = (uint8_t) (total_seconds / 3600);
+ total_seconds %= 3600;
+ *m = (uint8_t) (total_seconds / 60);
+ *s = (uint8_t) (total_seconds % 60);
}
#ifndef TIMED_REMOTE_TEST_BUILD
-uint32_t time_helper_seconds_until(uint8_t target_h, uint8_t target_m,
- uint8_t target_s) {
- DateTime now;
- furi_hal_rtc_get_datetime(&now);
-
- uint32_t now_seconds =
- time_helper_hms_to_seconds(now.hour, now.minute, now.second);
- uint32_t target_seconds =
- time_helper_hms_to_seconds(target_h, target_m, target_s);
-
- if (target_seconds < now_seconds) {
- /* Roll to next day */
- return (24 * 3600 - now_seconds) + target_seconds;
- }
-
- /* Target time is now or later today */
- return target_seconds - now_seconds;
+uint32_t
+time_until(uint8_t target_h, uint8_t target_m,
+ uint8_t target_s)
+{
+ DateTime now;
+ furi_hal_rtc_get_datetime(&now);
+
+ uint32_t now_seconds =
+ time_hms_sec(now.hour, now.minute, now.second);
+ uint32_t target_seconds =
+ time_hms_sec(target_h, target_m, target_s);
+
+ if (target_seconds < now_seconds)
+ {
+ /* Roll to next day */
+ return (24 * 3600 - now_seconds) + target_seconds;
+ }
+
+ /* Target time is now or later today */
+ return target_seconds - now_seconds;
}
-void time_helper_generate_signal_name(char *buffer, size_t buffer_size) {
- DateTime now;
- furi_hal_rtc_get_datetime(&now);
+void
+time_name(char *buffer, size_t buffer_size)
+{
+ DateTime now;
+ furi_hal_rtc_get_datetime(&now);
- snprintf(buffer, buffer_size, "IR_%04d%02d%02d_%02d%02d%02d", now.year,
- now.month, now.day, now.hour, now.minute, now.second);
+ snprintf(buffer, buffer_size, "IR_%04d%02d%02d_%02d%02d%02d", now.year,
+ now.month, now.day, now.hour, now.minute, now.second);
}
#endif
diff --git a/helpers/time_helper.h b/helpers/time_helper.h
@@ -1,50 +1,12 @@
#pragma once
-#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-/**
- * Convert hours, minutes, seconds to total seconds.
- *
- * @param h Hours (0-23)
- * @param m Minutes (0-59)
- * @param s Seconds (0-59)
- * @return Total seconds
- */
-uint32_t time_helper_hms_to_seconds(uint8_t h, uint8_t m, uint8_t s);
-
-/**
- * Convert total seconds to hours, minutes, seconds.
- *
- * @param total_seconds Total seconds to convert
- * @param h Output: hours (0-23, wraps at 24)
- * @param m Output: minutes (0-59)
- * @param s Output: seconds (0-59)
- */
-void time_helper_seconds_to_hms(uint32_t total_seconds, uint8_t *h, uint8_t *m,
- uint8_t *s);
+uint32_t time_hms_sec(uint8_t h, uint8_t m, uint8_t s);
+void time_sec_hms(uint32_t total_seconds, uint8_t *h, uint8_t *m, uint8_t *s);
#ifndef TIMED_REMOTE_TEST_BUILD
-/**
- * Calculate seconds remaining until a target time.
- * Uses Flipper's RTC. If target time has passed, rolls to next day.
- * If target time is exactly now, returns 0.
- *
- * @param target_h Target hour (0-23)
- * @param target_m Target minute (0-59)
- * @param target_s Target second (0-59)
- * @return Seconds until target (today or next day), or 0 if now
- */
-uint32_t time_helper_seconds_until(uint8_t target_h, uint8_t target_m,
- uint8_t target_s);
-
-/**
- * Generate a timestamp-based signal name placeholder.
- * Format: IR_YYYYMMDD_HHMMSS
- *
- * @param buffer Output buffer (must be at least 20 bytes)
- * @param buffer_size Size of buffer
- */
-void time_helper_generate_signal_name(char *buffer, size_t buffer_size);
+uint32_t time_until(uint8_t target_h, uint8_t target_m, uint8_t target_s);
+void time_name(char *buffer, size_t buffer_size);
#endif
diff --git a/scenes/scene_confirm.c b/scenes/scene_confirm.c
@@ -1,46 +1,40 @@
#include "../timed_remote.h"
#include "timed_remote_scene.h"
-static void confirm_popup_callback(void *context) {
- TimedRemoteApp *app = context;
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventConfirmDone);
+static void
+done_cb(void *context)
+{
+ TimedRemoteApp *a = context;
+ view_dispatcher_send_custom_event(a->vd, EvDone);
}
-void timed_remote_scene_confirm_on_enter(void *context) {
- TimedRemoteApp *app = context;
-
- popup_reset(app->popup);
- popup_set_header(app->popup, "Signal Sent!", 64, 20, AlignCenter,
- AlignCenter);
- popup_set_text(app->popup, app->signal_name, 64, 35, AlignCenter,
- AlignCenter);
- popup_set_timeout(app->popup, 2000); /* 2 seconds */
- popup_set_context(app->popup, app);
- popup_set_callback(app->popup, confirm_popup_callback);
- popup_enable_timeout(app->popup);
-
- view_dispatcher_switch_to_view(app->view_dispatcher, TimedRemoteViewPopup);
+void
+scene_done_enter(void *context)
+{
+ TimedRemoteApp *a = context;
+ popup_reset(a->popup);
+ popup_set_header(a->popup, "Signal Sent!", 64, 20, AlignCenter, AlignCenter);
+ popup_set_text(a->popup, a->sig, 64, 35, AlignCenter, AlignCenter);
+ popup_set_timeout(a->popup, 2000);
+ popup_set_context(a->popup, a);
+ popup_set_callback(a->popup, done_cb);
+ popup_enable_timeout(a->popup);
+ view_dispatcher_switch_to_view(a->vd, ViewPop);
}
-bool timed_remote_scene_confirm_on_event(void *context,
- SceneManagerEvent event) {
- TimedRemoteApp *app = context;
- bool consumed = false;
-
- if (event.type == SceneManagerEventTypeCustom) {
- if (event.event == TimedRemoteEventConfirmDone) {
- /* Return to file browser */
- scene_manager_search_and_switch_to_previous_scene(
- app->scene_manager, TimedRemoteSceneIrBrowse);
- consumed = true;
- }
- }
-
- return consumed;
+bool
+scene_done_event(void *context, SceneManagerEvent event)
+{
+ TimedRemoteApp *a = context;
+ if (event.type != SceneManagerEventTypeCustom || event.event != EvDone)
+ return false;
+ scene_manager_search_and_switch_to_previous_scene(a->sm, ScBrowse);
+ return true;
}
-void timed_remote_scene_confirm_on_exit(void *context) {
- TimedRemoteApp *app = context;
- popup_reset(app->popup);
+void
+scene_done_exit(void *context)
+{
+ TimedRemoteApp *a = context;
+ popup_reset(a->popup);
}
diff --git a/scenes/scene_ir_browse.c b/scenes/scene_ir_browse.c
@@ -2,68 +2,64 @@
#include "../timed_remote.h"
#include "timed_remote_scene.h"
-#define IR_DIR_PATH "/ext/infrared"
+#define IR_DIR "/ext/infrared"
-static FuriString **file_list = NULL;
-static size_t file_count = 0;
+static FuriString **files;
+static size_t nfiles;
-static void ir_browse_callback(void *context, uint32_t index) {
- TimedRemoteApp *app = context;
- if (index < file_count) {
- /* Store selected file path */
- snprintf(app->selected_file_path, sizeof(app->selected_file_path), "%s/%s",
- IR_DIR_PATH, furi_string_get_cstr(file_list[index]));
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventFileSelected);
- }
+static void
+pick_file(void *context, uint32_t i)
+{
+ TimedRemoteApp *a = context;
+ if (i >= nfiles)
+ return;
+ snprintf(a->file, sizeof(a->file), "%s/%s", IR_DIR, furi_string_get_cstr(files[i]));
+ view_dispatcher_send_custom_event(a->vd, EvFile);
}
-void timed_remote_scene_ir_browse_on_enter(void *context) {
- TimedRemoteApp *app = context;
+void
+scene_browse_enter(void *context)
+{
+ TimedRemoteApp *a = context;
+ submenu_reset(a->submenu);
+ submenu_set_header(a->submenu, "Select IR File");
- submenu_reset(app->submenu);
- submenu_set_header(app->submenu, "Select IR File");
+ if (ir_files(IR_DIR, &files, &nfiles))
+ {
+ if (nfiles == 0)
+ {
+ submenu_add_item(a->submenu, "(No IR files found)", 0, NULL, NULL);
+ } else
+ {
+ for (size_t i = 0; i < nfiles; i++)
+ submenu_add_item(a->submenu, furi_string_get_cstr(files[i]), i, pick_file, a);
+ }
+ } else
+ {
+ submenu_add_item(a->submenu, "(Error reading directory)", 0, NULL, NULL);
+ }
- /* Get list of .ir files */
- if (ir_helper_list_files(IR_DIR_PATH, &file_list, &file_count)) {
- if (file_count == 0) {
- submenu_add_item(app->submenu, "(No IR files found)", 0, NULL, NULL);
- } else {
- for (size_t i = 0; i < file_count; i++) {
- submenu_add_item(app->submenu, furi_string_get_cstr(file_list[i]), i,
- ir_browse_callback, app);
- }
- }
- } else {
- submenu_add_item(app->submenu, "(Error reading directory)", 0, NULL, NULL);
- }
-
- view_dispatcher_switch_to_view(app->view_dispatcher, TimedRemoteViewSubmenu);
+ view_dispatcher_switch_to_view(a->vd, ViewMenu);
}
-bool timed_remote_scene_ir_browse_on_event(void *context,
- SceneManagerEvent event) {
- TimedRemoteApp *app = context;
- bool consumed = false;
-
- if (event.type == SceneManagerEventTypeCustom) {
- if (event.event == TimedRemoteEventFileSelected) {
- scene_manager_next_scene(app->scene_manager, TimedRemoteSceneIrSelect);
- consumed = true;
- }
- }
-
- return consumed;
+bool
+scene_browse_event(void *context, SceneManagerEvent event)
+{
+ TimedRemoteApp *a = context;
+ if (event.type != SceneManagerEventTypeCustom || event.event != EvFile)
+ return false;
+ scene_manager_next_scene(a->sm, ScSelect);
+ return true;
}
-void timed_remote_scene_ir_browse_on_exit(void *context) {
- TimedRemoteApp *app = context;
- submenu_reset(app->submenu);
-
- /* Free file list */
- if (file_list) {
- ir_helper_free_file_list(file_list, file_count);
- file_list = NULL;
- file_count = 0;
- }
+void
+scene_browse_exit(void *context)
+{
+ TimedRemoteApp *a = context;
+ submenu_reset(a->submenu);
+ if (!files)
+ return;
+ ir_files_free(files, nfiles);
+ files = NULL;
+ nfiles = 0;
}
diff --git a/scenes/scene_ir_select.c b/scenes/scene_ir_select.c
@@ -2,79 +2,74 @@
#include "../timed_remote.h"
#include "timed_remote_scene.h"
-static IrSignalList *signal_list = NULL;
+static IrSignalList *sigs;
-static void ir_select_callback(void *context, uint32_t index) {
- TimedRemoteApp *app = context;
- if (signal_list && index < signal_list->count) {
- /* Free any previous signal */
- if (app->ir_signal) {
- infrared_signal_free(app->ir_signal);
- }
- /* Copy the selected signal */
- app->ir_signal = infrared_signal_alloc();
- infrared_signal_set_signal(app->ir_signal,
- signal_list->items[index].signal);
+static void
+pick_sig(void *context, uint32_t i)
+{
+ TimedRemoteApp *a = context;
+ if (!sigs || i >= sigs->count)
+ return;
- /* Copy signal name */
- strncpy(app->signal_name,
- furi_string_get_cstr(signal_list->items[index].name),
- sizeof(app->signal_name) - 1);
- app->signal_name[sizeof(app->signal_name) - 1] = '\0';
+ if (a->ir)
+ infrared_signal_free(a->ir);
+ a->ir = infrared_signal_alloc();
+ if (!a->ir)
+ return;
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventSignalSelected);
- }
+ infrared_signal_set_signal(a->ir, sigs->items[i].signal);
+ strncpy(a->sig, furi_string_get_cstr(sigs->items[i].name), sizeof(a->sig) - 1);
+ a->sig[sizeof(a->sig) - 1] = '\0';
+ view_dispatcher_send_custom_event(a->vd, EvSig);
}
-void timed_remote_scene_ir_select_on_enter(void *context) {
- TimedRemoteApp *app = context;
+void
+scene_select_enter(void *context)
+{
+ TimedRemoteApp *a = context;
+ submenu_reset(a->submenu);
+ submenu_set_header(a->submenu, "Select Signal");
- submenu_reset(app->submenu);
- submenu_set_header(app->submenu, "Select Signal");
+ sigs = ir_list_alloc();
+ if (!sigs)
+ {
+ submenu_add_item(a->submenu, "(Out of memory)", 0, NULL, NULL);
+ } else if (ir_load(a->file, sigs))
+ {
+ if (sigs->count == 0)
+ {
+ submenu_add_item(a->submenu, "(No signals in file)", 0, NULL, NULL);
+ } else
+ {
+ for (size_t i = 0; i < sigs->count; i++)
+ submenu_add_item(
+ a->submenu, furi_string_get_cstr(sigs->items[i].name), i, pick_sig, a);
+ }
+ } else
+ {
+ submenu_add_item(a->submenu, "(Error reading file)", 0, NULL, NULL);
+ }
- /* Load signals from selected file */
- signal_list = ir_signal_list_alloc();
- if (ir_helper_load_file(app->selected_file_path, signal_list)) {
- if (signal_list->count == 0) {
- submenu_add_item(app->submenu, "(No signals in file)", 0, NULL, NULL);
- } else {
- for (size_t i = 0; i < signal_list->count; i++) {
- submenu_add_item(app->submenu,
- furi_string_get_cstr(signal_list->items[i].name), i,
- ir_select_callback, app);
- }
- }
- } else {
- submenu_add_item(app->submenu, "(Error reading file)", 0, NULL, NULL);
- }
-
- view_dispatcher_switch_to_view(app->view_dispatcher, TimedRemoteViewSubmenu);
+ view_dispatcher_switch_to_view(a->vd, ViewMenu);
}
-bool timed_remote_scene_ir_select_on_event(void *context,
- SceneManagerEvent event) {
- TimedRemoteApp *app = context;
- bool consumed = false;
-
- if (event.type == SceneManagerEventTypeCustom) {
- if (event.event == TimedRemoteEventSignalSelected) {
- scene_manager_next_scene(app->scene_manager,
- TimedRemoteSceneTimerConfig);
- consumed = true;
- }
- }
-
- return consumed;
+bool
+scene_select_event(void *context, SceneManagerEvent event)
+{
+ TimedRemoteApp *a = context;
+ if (event.type != SceneManagerEventTypeCustom || event.event != EvSig)
+ return false;
+ scene_manager_next_scene(a->sm, ScCfg);
+ return true;
}
-void timed_remote_scene_ir_select_on_exit(void *context) {
- TimedRemoteApp *app = context;
- submenu_reset(app->submenu);
-
- /* Free signal list */
- if (signal_list) {
- ir_signal_list_free(signal_list);
- signal_list = NULL;
- }
+void
+scene_select_exit(void *context)
+{
+ TimedRemoteApp *a = context;
+ submenu_reset(a->submenu);
+ if (!sigs)
+ return;
+ ir_list_free(sigs);
+ sigs = NULL;
}
diff --git a/scenes/scene_timer_config.c b/scenes/scene_timer_config.c
@@ -1,202 +1,199 @@
#include "../timed_remote.h"
#include "timed_remote_scene.h"
-enum {
- TimerConfigIndexMode,
- TimerConfigIndexHours,
- TimerConfigIndexMinutes,
- TimerConfigIndexSeconds,
- TimerConfigIndexRepeat,
- TimerConfigIndexConfirm,
+enum
+{
+ IdxMode,
+ IdxH,
+ IdxM,
+ IdxS,
+ IdxRepeat,
+ IdxStart,
};
-static void timer_config_mode_change(VariableItem *item) {
- TimedRemoteApp *app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- app->timer_mode = (index == 0) ? TimerModeCountdown : TimerModeScheduled;
- variable_item_set_current_value_text(
- item, app->timer_mode == TimerModeCountdown ? "Countdown" : "Scheduled");
-
- /* Disable repeat in scheduled mode */
- if (app->timer_mode == TimerModeScheduled) {
- app->repeat_count = 0;
- }
-
- /* Trigger rebuild to show/hide repeat options */
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventModeChanged);
-}
-
-static void timer_config_hours_change(VariableItem *item) {
- TimedRemoteApp *app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- app->hours = index;
- char buf[4];
- snprintf(buf, sizeof(buf), "%02d", app->hours);
- variable_item_set_current_value_text(item, buf);
-}
-
-static void timer_config_minutes_change(VariableItem *item) {
- TimedRemoteApp *app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- app->minutes = index;
- char buf[4];
- snprintf(buf, sizeof(buf), "%02d", app->minutes);
- variable_item_set_current_value_text(item, buf);
-}
-
-static void timer_config_seconds_change(VariableItem *item) {
- TimedRemoteApp *app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- app->seconds = index;
- char buf[4];
- snprintf(buf, sizeof(buf), "%02d", app->seconds);
- variable_item_set_current_value_text(item, buf);
-}
-
-static void timer_config_repeat_change(VariableItem *item) {
- TimedRemoteApp *app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
-
- char buf[16];
- if (index == 0) {
- /* Off */
- app->repeat_count = 0;
- snprintf(buf, sizeof(buf), "Off");
- } else if (index == 1) {
- /* Unlimited */
- app->repeat_count = 255;
- snprintf(buf, sizeof(buf), "Unlimited");
- } else {
- /* 1, 2, 3, ... 99 */
- app->repeat_count = index - 1;
- snprintf(buf, sizeof(buf), "%d", app->repeat_count);
- }
- variable_item_set_current_value_text(item, buf);
-}
-
-static void timer_config_enter_callback(void *context, uint32_t index) {
- TimedRemoteApp *app = context;
- /* In countdown mode, confirm is at index 5, in scheduled mode it's at index 4 */
- uint32_t confirm_index = app->timer_mode == TimerModeCountdown
- ? TimerConfigIndexConfirm
- : (TimerConfigIndexSeconds + 1);
- if (index == confirm_index) {
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventTimerConfigured);
- }
-}
-
-static void build_timer_config_list(TimedRemoteApp *app) {
- VariableItem *item;
- char buf[16];
-
- variable_item_list_reset(app->variable_item_list);
-
- /* Mode: Countdown / Scheduled */
- item = variable_item_list_add(app->variable_item_list, "Mode", 2,
- timer_config_mode_change, app);
- variable_item_set_current_value_index(
- item, app->timer_mode == TimerModeCountdown ? 0 : 1);
- variable_item_set_current_value_text(
- item, app->timer_mode == TimerModeCountdown ? "Countdown" : "Scheduled");
-
- /* Hours: 0-23 */
- item = variable_item_list_add(app->variable_item_list, "Hours", 24,
- timer_config_hours_change, app);
- variable_item_set_current_value_index(item, app->hours);
- snprintf(buf, sizeof(buf), "%02d", app->hours);
- variable_item_set_current_value_text(item, buf);
-
- /* Minutes: 0-59 */
- item = variable_item_list_add(app->variable_item_list, "Minutes", 60,
- timer_config_minutes_change, app);
- variable_item_set_current_value_index(item, app->minutes);
- snprintf(buf, sizeof(buf), "%02d", app->minutes);
- variable_item_set_current_value_text(item, buf);
-
- /* Seconds: 0-59 */
- item = variable_item_list_add(app->variable_item_list, "Seconds", 60,
- timer_config_seconds_change, app);
- variable_item_set_current_value_index(item, app->seconds);
- snprintf(buf, sizeof(buf), "%02d", app->seconds);
- variable_item_set_current_value_text(item, buf);
-
- /* Repeat options - only for countdown mode */
- if (app->timer_mode == TimerModeCountdown) {
- /* Repeat: Off, Unlimited, 1, 2, 3, ... 99 (total 101 values) */
- item = variable_item_list_add(app->variable_item_list, "Repeat", 101,
- timer_config_repeat_change, app);
-
- /* Convert repeat_count to index */
- uint8_t repeat_index;
- if (app->repeat_count == 0) {
- repeat_index = 0; /* Off */
- } else if (app->repeat_count == 255) {
- repeat_index = 1; /* Unlimited */
- } else {
- repeat_index = app->repeat_count + 1; /* 1-99 */
- }
- variable_item_set_current_value_index(item, repeat_index);
-
- /* Set display text */
- if (app->repeat_count == 0) {
- variable_item_set_current_value_text(item, "Off");
- } else if (app->repeat_count == 255) {
- variable_item_set_current_value_text(item, "Unlimited");
- } else {
- snprintf(buf, sizeof(buf), "%d", app->repeat_count);
- variable_item_set_current_value_text(item, buf);
- }
- }
-
- /* Confirm button */
- variable_item_list_add(app->variable_item_list, ">> Start Timer <<", 0, NULL,
- NULL);
-
- variable_item_list_set_enter_callback(app->variable_item_list,
- timer_config_enter_callback, app);
-}
-
-void timed_remote_scene_timer_config_on_enter(void *context) {
- TimedRemoteApp *app = context;
- build_timer_config_list(app);
- view_dispatcher_switch_to_view(app->view_dispatcher,
- TimedRemoteViewVariableItemList);
-}
-
-bool timed_remote_scene_timer_config_on_event(void *context,
- SceneManagerEvent event) {
- TimedRemoteApp *app = context;
- bool consumed = false;
-
- if (event.type == SceneManagerEventTypeCustom) {
- if (event.event == TimedRemoteEventModeChanged) {
- /* Rebuild the list to show/hide repeat options */
- build_timer_config_list(app);
- consumed = true;
- } else if (event.event == TimedRemoteEventTimerConfigured) {
- /* Initialize repeats remaining based on repeat_count encoding */
- if (app->repeat_count == 0) {
- /* Off - single execution */
- app->repeats_remaining = 1;
- } else if (app->repeat_count == 255) {
- /* Unlimited */
- app->repeats_remaining = 255;
- } else {
- /* Fixed count (1-99): initial + N repeats = N+1 total executions */
- app->repeats_remaining = app->repeat_count + 1;
- }
- scene_manager_next_scene(app->scene_manager,
- TimedRemoteSceneTimerRunning);
- consumed = true;
- }
- }
-
- return consumed;
-}
-
-void timed_remote_scene_timer_config_on_exit(void *context) {
- TimedRemoteApp *app = context;
- variable_item_list_reset(app->variable_item_list);
+#define REP_OFF 0U
+#define REP_INF 255U
+
+static void
+set2(VariableItem *it, uint8_t v)
+{
+ char s[4];
+ snprintf(s, sizeof(s), "%02d", v);
+ variable_item_set_current_value_text(it, s);
+}
+
+static uint8_t
+rep_from_idx(uint8_t i)
+{
+ if (i == 0)
+ return REP_OFF;
+ if (i == 1)
+ return REP_INF;
+ return i - 1;
+}
+
+static uint8_t
+idx_from_rep(uint8_t r)
+{
+ if (r == REP_OFF)
+ return 0;
+ if (r == REP_INF)
+ return 1;
+ return r + 1;
+}
+
+static void
+set_rep(VariableItem *it, uint8_t r)
+{
+ if (r == REP_OFF)
+ {
+ variable_item_set_current_value_text(it, "Off");
+ return;
+ }
+ if (r == REP_INF)
+ {
+ variable_item_set_current_value_text(it, "Unlimited");
+ return;
+ }
+ char s[16];
+ snprintf(s, sizeof(s), "%u", r);
+ variable_item_set_current_value_text(it, s);
+}
+
+static uint32_t
+start_idx(const TimedRemoteApp *a)
+{
+ if (a->mode == ModeDown)
+ return IdxStart;
+ return IdxRepeat;
+}
+
+static void
+mode_chg(VariableItem *it)
+{
+ TimedRemoteApp *a = variable_item_get_context(it);
+ a->mode = variable_item_get_current_value_index(it) == 0
+ ? ModeDown
+ : ModeSched;
+ variable_item_set_current_value_text(
+ it, a->mode == ModeDown ? "Countdown" : "Scheduled");
+ if (a->mode == ModeSched)
+ a->repeat = REP_OFF;
+ view_dispatcher_send_custom_event(a->vd, EvMode);
+}
+
+static void
+h_chg(VariableItem *it)
+{
+ TimedRemoteApp *a = variable_item_get_context(it);
+ a->h = variable_item_get_current_value_index(it);
+ set2(it, a->h);
+}
+
+static void
+m_chg(VariableItem *it)
+{
+ TimedRemoteApp *a = variable_item_get_context(it);
+ a->m = variable_item_get_current_value_index(it);
+ set2(it, a->m);
+}
+
+static void
+s_chg(VariableItem *it)
+{
+ TimedRemoteApp *a = variable_item_get_context(it);
+ a->s = variable_item_get_current_value_index(it);
+ set2(it, a->s);
+}
+
+static void
+rep_chg(VariableItem *it)
+{
+ TimedRemoteApp *a = variable_item_get_context(it);
+ a->repeat = rep_from_idx(variable_item_get_current_value_index(it));
+ set_rep(it, a->repeat);
+}
+
+static void
+enter_cb(void *ctx, uint32_t i)
+{
+ TimedRemoteApp *a = ctx;
+ if (i != start_idx(a))
+ return;
+ view_dispatcher_send_custom_event(a->vd, EvCfg);
+}
+
+static void
+add_tm(VariableItemList *l, const char *n, uint8_t nvals, uint8_t v, VariableItemChangeCallback cb, TimedRemoteApp *a)
+{
+ VariableItem *it = variable_item_list_add(l, n, nvals, cb, a);
+ variable_item_set_current_value_index(it, v);
+ set2(it, v);
+}
+
+static void
+build(TimedRemoteApp *a)
+{
+ variable_item_list_reset(a->vlist);
+
+ VariableItem *it = variable_item_list_add(a->vlist, "Mode", 2, mode_chg, a);
+ variable_item_set_current_value_index(it, a->mode == ModeDown ? 0 : 1);
+ variable_item_set_current_value_text(
+ it, a->mode == ModeDown ? "Countdown" : "Scheduled");
+
+ add_tm(a->vlist, "Hours", 24, a->h, h_chg, a);
+ add_tm(a->vlist, "Minutes", 60, a->m, m_chg, a);
+ add_tm(a->vlist, "Seconds", 60, a->s, s_chg, a);
+
+ if (a->mode == ModeDown)
+ {
+ it = variable_item_list_add(a->vlist, "Repeat", 101, rep_chg, a);
+ variable_item_set_current_value_index(it, idx_from_rep(a->repeat));
+ set_rep(it, a->repeat);
+ }
+
+ variable_item_list_add(a->vlist, ">> Start Timer <<", 0, NULL, NULL);
+ variable_item_list_set_enter_callback(a->vlist, enter_cb, a);
+}
+
+void
+scene_cfg_enter(void *context)
+{
+ TimedRemoteApp *a = context;
+ build(a);
+ view_dispatcher_switch_to_view(a->vd, ViewList);
+}
+
+bool
+scene_cfg_event(void *context, SceneManagerEvent event)
+{
+ TimedRemoteApp *a = context;
+ if (event.type != SceneManagerEventTypeCustom)
+ return false;
+ if (event.event == EvMode)
+ {
+ build(a);
+ return true;
+ }
+ if (event.event != EvCfg)
+ return false;
+ if (a->repeat == REP_OFF)
+ {
+ a->repeat_left = 1;
+ } else if (a->repeat == REP_INF)
+ {
+ a->repeat_left = REP_INF;
+ } else
+ {
+ a->repeat_left = a->repeat + 1;
+ }
+ scene_manager_next_scene(a->sm, ScRun);
+ return true;
+}
+
+void
+scene_cfg_exit(void *context)
+{
+ TimedRemoteApp *a = context;
+ variable_item_list_reset(a->vlist);
}
diff --git a/scenes/scene_timer_running.c b/scenes/scene_timer_running.c
@@ -3,134 +3,130 @@
#include "../timed_remote.h"
#include "timed_remote_scene.h"
-static void timer_callback(void *context) {
- TimedRemoteApp *app = context;
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventTimerTick);
+static void
+tick_cb(void *context)
+{
+ TimedRemoteApp *a = context;
+ view_dispatcher_send_custom_event(a->vd, EvTick);
}
-static void update_display(TimedRemoteApp *app) {
- uint8_t h, m, s;
- time_helper_seconds_to_hms(app->seconds_remaining, &h, &m, &s);
-
- char time_str[16];
- snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", h, m, s);
-
- widget_reset(app->widget);
- widget_add_string_element(app->widget, 64, 5, AlignCenter, AlignTop,
- FontSecondary, app->signal_name);
- widget_add_string_element(app->widget, 64, 25, AlignCenter, AlignTop,
- FontBigNumbers, time_str);
-
- if (app->repeat_count > 0 && app->repeat_count < 255) {
- /* Fixed repeat count (1-99) */
- char repeat_str[24];
- snprintf(repeat_str, sizeof(repeat_str), "Repeat: %d/%d",
- app->repeat_count - app->repeats_remaining + 1, app->repeat_count);
- widget_add_string_element(app->widget, 64, 52, AlignCenter, AlignTop,
- FontSecondary, repeat_str);
- } else if (app->repeat_count == 255) {
- /* Unlimited repeat */
- widget_add_string_element(app->widget, 64, 52, AlignCenter, AlignTop,
- FontSecondary, "Repeat: Unlimited");
- }
+static uint32_t
+countdown(const TimedRemoteApp *a)
+{
+ return time_hms_sec(a->h, a->m, a->s);
}
-void timed_remote_scene_timer_running_on_enter(void *context) {
- TimedRemoteApp *app = context;
-
- /* Calculate initial remaining time */
- if (app->timer_mode == TimerModeCountdown) {
- app->seconds_remaining =
- time_helper_hms_to_seconds(app->hours, app->minutes, app->seconds);
- } else {
- /* Scheduled mode - calculate time until target */
- app->seconds_remaining =
- time_helper_seconds_until(app->hours, app->minutes, app->seconds);
- if (app->seconds_remaining == 0) {
- /* Target time already passed - fire immediately */
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventTimerFired);
- return;
- }
- }
-
- /* Initialize repeat tracking for non-repeat or scheduled */
- if (app->repeat_count == 0) {
- app->repeats_remaining = 1;
- }
-
- update_display(app);
- view_dispatcher_switch_to_view(app->view_dispatcher, TimedRemoteViewWidget);
-
- /* Start 1-second timer */
- app->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app);
- furi_timer_start(app->timer, furi_kernel_get_tick_frequency()); /* 1 second */
+static void
+redraw(TimedRemoteApp *a)
+{
+ uint8_t h, m, s;
+ time_sec_hms(a->left, &h, &m, &s);
+
+ char t[16];
+ snprintf(t, sizeof(t), "%02d:%02d:%02d", h, m, s);
+
+ widget_reset(a->widget);
+ widget_add_string_element(a->widget, 64, 5, AlignCenter, AlignTop, FontSecondary, a->sig);
+ widget_add_string_element(a->widget, 64, 25, AlignCenter, AlignTop, FontBigNumbers, t);
+ if (a->repeat > 0 && a->repeat < 255)
+ {
+ char r[24];
+ snprintf(r, sizeof(r), "Repeat: %d/%d", a->repeat - a->repeat_left + 1, a->repeat);
+ widget_add_string_element(a->widget, 64, 52, AlignCenter, AlignTop, FontSecondary, r);
+ } else if (a->repeat == 255)
+ {
+ widget_add_string_element(
+ a->widget, 64, 52, AlignCenter, AlignTop, FontSecondary, "Repeat: Unlimited");
+ }
}
-bool timed_remote_scene_timer_running_on_event(void *context,
- SceneManagerEvent event) {
- TimedRemoteApp *app = context;
- bool consumed = false;
-
- if (event.type == SceneManagerEventTypeCustom) {
- if (event.event == TimedRemoteEventTimerTick) {
- if (app->seconds_remaining > 0) {
- app->seconds_remaining--;
- update_display(app);
- }
-
- if (app->seconds_remaining == 0) {
- /* Timer fired! */
- view_dispatcher_send_custom_event(app->view_dispatcher,
- TimedRemoteEventTimerFired);
- }
- consumed = true;
- } else if (event.event == TimedRemoteEventTimerFired) {
- /* Transmit IR signal */
- if (app->ir_signal) {
- ir_helper_transmit(app->ir_signal);
- }
-
- if (app->repeat_count == 255) {
- /* Unlimited repeat */
- app->seconds_remaining =
- time_helper_hms_to_seconds(app->hours, app->minutes, app->seconds);
- update_display(app);
- } else {
- app->repeats_remaining--;
-
- if (app->repeat_count != 0 && app->repeats_remaining > 0) {
- /* Reset countdown for next repeat */
- app->seconds_remaining = time_helper_hms_to_seconds(
- app->hours, app->minutes, app->seconds);
- update_display(app);
- } else {
- /* Done - show confirmation */
- scene_manager_next_scene(app->scene_manager, TimedRemoteSceneConfirm);
- }
- }
- consumed = true;
- }
- } else if (event.type == SceneManagerEventTypeBack) {
- /* User pressed Back - cancel timer */
- scene_manager_search_and_switch_to_previous_scene(app->scene_manager,
- TimedRemoteSceneIrBrowse);
- consumed = true;
- }
-
- return consumed;
+void
+scene_run_enter(void *context)
+{
+ TimedRemoteApp *a = context;
+
+ if (a->mode == ModeDown)
+ {
+ a->left = countdown(a);
+ } else
+ {
+ a->left = time_until(a->h, a->m, a->s);
+ if (a->left == 0)
+ {
+ view_dispatcher_send_custom_event(a->vd, EvFire);
+ return;
+ }
+ }
+
+ if (a->repeat == 0)
+ a->repeat_left = 1;
+
+ redraw(a);
+ view_dispatcher_switch_to_view(a->vd, ViewRun);
+
+ a->timer = furi_timer_alloc(tick_cb, FuriTimerTypePeriodic, a);
+ if (!a->timer)
+ return;
+ furi_timer_start(a->timer, furi_kernel_get_tick_frequency());
}
-void timed_remote_scene_timer_running_on_exit(void *context) {
- TimedRemoteApp *app = context;
-
- /* Stop and free timer */
- if (app->timer) {
- furi_timer_stop(app->timer);
- furi_timer_free(app->timer);
- app->timer = NULL;
- }
+bool
+scene_run_event(void *context, SceneManagerEvent event)
+{
+ TimedRemoteApp *a = context;
+ if (event.type == SceneManagerEventTypeBack)
+ {
+ scene_manager_search_and_switch_to_previous_scene(a->sm, ScBrowse);
+ return true;
+ }
+ if (event.type != SceneManagerEventTypeCustom)
+ return false;
+ if (event.event == EvTick)
+ {
+ if (a->left > 0)
+ {
+ a->left--;
+ redraw(a);
+ }
+ if (a->left == 0)
+ view_dispatcher_send_custom_event(a->vd, EvFire);
+ return true;
+ }
+ if (event.event != EvFire)
+ return false;
+
+ if (a->ir)
+ ir_tx(a->ir);
+
+ if (a->repeat == 255)
+ {
+ a->left = countdown(a);
+ redraw(a);
+ return true;
+ }
+
+ if (a->repeat_left > 0)
+ a->repeat_left--;
+ if (a->repeat != 0 && a->repeat_left > 0)
+ {
+ a->left = countdown(a);
+ redraw(a);
+ return true;
+ }
+
+ scene_manager_next_scene(a->sm, ScDone);
+ return true;
+}
- widget_reset(app->widget);
+void
+scene_run_exit(void *context)
+{
+ TimedRemoteApp *a = context;
+ if (a->timer)
+ {
+ furi_timer_stop(a->timer);
+ furi_timer_free(a->timer);
+ a->timer = NULL;
+ }
+ widget_reset(a->widget);
}
diff --git a/scenes/timed_remote_scene.c b/scenes/timed_remote_scene.c
@@ -1,35 +1,32 @@
#include "timed_remote_scene.h"
-/* Scene handler tables */
-
-void (*const timed_remote_scene_on_enter_handlers[])(void *) = {
- timed_remote_scene_ir_browse_on_enter,
- timed_remote_scene_ir_select_on_enter,
- timed_remote_scene_timer_config_on_enter,
- timed_remote_scene_timer_running_on_enter,
- timed_remote_scene_confirm_on_enter,
+static void (*const enter[])(void *) = {
+ [ScBrowse] = scene_browse_enter,
+ [ScSelect] = scene_select_enter,
+ [ScCfg] = scene_cfg_enter,
+ [ScRun] = scene_run_enter,
+ [ScDone] = scene_done_enter,
};
-bool (*const timed_remote_scene_on_event_handlers[])(void *,
- SceneManagerEvent) = {
- timed_remote_scene_ir_browse_on_event,
- timed_remote_scene_ir_select_on_event,
- timed_remote_scene_timer_config_on_event,
- timed_remote_scene_timer_running_on_event,
- timed_remote_scene_confirm_on_event,
+static bool (*const event[])(void *, SceneManagerEvent) = {
+ [ScBrowse] = scene_browse_event,
+ [ScSelect] = scene_select_event,
+ [ScCfg] = scene_cfg_event,
+ [ScRun] = scene_run_event,
+ [ScDone] = scene_done_event,
};
-void (*const timed_remote_scene_on_exit_handlers[])(void *) = {
- timed_remote_scene_ir_browse_on_exit,
- timed_remote_scene_ir_select_on_exit,
- timed_remote_scene_timer_config_on_exit,
- timed_remote_scene_timer_running_on_exit,
- timed_remote_scene_confirm_on_exit,
+static void (*const on_exit[])(void *) = {
+ [ScBrowse] = scene_browse_exit,
+ [ScSelect] = scene_select_exit,
+ [ScCfg] = scene_cfg_exit,
+ [ScRun] = scene_run_exit,
+ [ScDone] = scene_done_exit,
};
-const SceneManagerHandlers timed_remote_scene_handlers = {
- .on_enter_handlers = timed_remote_scene_on_enter_handlers,
- .on_event_handlers = timed_remote_scene_on_event_handlers,
- .on_exit_handlers = timed_remote_scene_on_exit_handlers,
- .scene_num = TimedRemoteSceneCount,
+const SceneManagerHandlers scene_handlers = {
+ .on_enter_handlers = enter,
+ .on_event_handlers = event,
+ .on_exit_handlers = on_exit,
+ .scene_num = ScN,
};
diff --git a/scenes/timed_remote_scene.h b/scenes/timed_remote_scene.h
@@ -2,59 +2,39 @@
#include <gui/scene_manager.h>
-/* Scene IDs */
-typedef enum {
- TimedRemoteSceneIrBrowse,
- TimedRemoteSceneIrSelect,
- TimedRemoteSceneTimerConfig,
- TimedRemoteSceneTimerRunning,
- TimedRemoteSceneConfirm,
- TimedRemoteSceneCount,
-} TimedRemoteScene;
-
-/* Scene event IDs */
-typedef enum {
- TimedRemoteSceneEventConsumed = true,
- TimedRemoteSceneEventNotConsumed = false,
-} TimedRemoteSceneEvent;
-
-/* Custom events */
-typedef enum {
- /* File/signal selected */
- TimedRemoteEventFileSelected,
- TimedRemoteEventSignalSelected,
- /* Timer configuration */
- TimedRemoteEventModeChanged,
- TimedRemoteEventTimerConfigured,
- /* Timer events */
- TimedRemoteEventTimerTick,
- TimedRemoteEventTimerFired,
- /* Confirmation */
- TimedRemoteEventConfirmDone,
-} TimedRemoteCustomEvent;
-
-/* Scene handlers - declared extern, defined in individual scene files */
-extern void timed_remote_scene_ir_browse_on_enter(void *context);
-extern bool timed_remote_scene_ir_browse_on_event(void *context,
- SceneManagerEvent event);
-extern void timed_remote_scene_ir_browse_on_exit(void *context);
-
-extern void timed_remote_scene_ir_select_on_enter(void *context);
-extern bool timed_remote_scene_ir_select_on_event(void *context,
- SceneManagerEvent event);
-extern void timed_remote_scene_ir_select_on_exit(void *context);
-
-extern void timed_remote_scene_timer_config_on_enter(void *context);
-extern bool timed_remote_scene_timer_config_on_event(void *context,
- SceneManagerEvent event);
-extern void timed_remote_scene_timer_config_on_exit(void *context);
-
-extern void timed_remote_scene_timer_running_on_enter(void *context);
-extern bool timed_remote_scene_timer_running_on_event(void *context,
- SceneManagerEvent event);
-extern void timed_remote_scene_timer_running_on_exit(void *context);
-
-extern void timed_remote_scene_confirm_on_enter(void *context);
-extern bool timed_remote_scene_confirm_on_event(void *context,
- SceneManagerEvent event);
-extern void timed_remote_scene_confirm_on_exit(void *context);
+typedef enum
+{
+ ScBrowse,
+ ScSelect,
+ ScCfg,
+ ScRun,
+ ScDone,
+ ScN,
+} SceneId;
+
+typedef enum
+{
+ EvFile,
+ EvSig,
+ EvMode,
+ EvCfg,
+ EvTick,
+ EvFire,
+ EvDone,
+} EvId;
+
+void scene_browse_enter(void *context);
+bool scene_browse_event(void *context, SceneManagerEvent event);
+void scene_browse_exit(void *context);
+void scene_select_enter(void *context);
+bool scene_select_event(void *context, SceneManagerEvent event);
+void scene_select_exit(void *context);
+void scene_cfg_enter(void *context);
+bool scene_cfg_event(void *context, SceneManagerEvent event);
+void scene_cfg_exit(void *context);
+void scene_run_enter(void *context);
+bool scene_run_event(void *context, SceneManagerEvent event);
+void scene_run_exit(void *context);
+void scene_done_enter(void *context);
+bool scene_done_event(void *context, SceneManagerEvent event);
+void scene_done_exit(void *context);
diff --git a/timed_remote.c b/timed_remote.c
@@ -1,120 +1,107 @@
#include "timed_remote.h"
#include "scenes/timed_remote_scene.h"
-extern const SceneManagerHandlers timed_remote_scene_handlers;
+extern const SceneManagerHandlers scene_handlers;
-/* View dispatcher navigation callback */
-static bool timed_remote_navigation_callback(void *context) {
- TimedRemoteApp *app = context;
- return scene_manager_handle_back_event(app->scene_manager);
+static bool
+nav_cb(void *context)
+{
+ TimedRemoteApp *app = context;
+ return scene_manager_handle_back_event(app->sm);
}
-/* View dispatcher custom event callback */
-static bool timed_remote_custom_event_callback(void *context,
- uint32_t custom_event) {
- TimedRemoteApp *app = context;
- return scene_manager_handle_custom_event(app->scene_manager, custom_event);
+static bool
+evt_cb(void *context, uint32_t evt)
+{
+ TimedRemoteApp *app = context;
+ return scene_manager_handle_custom_event(app->sm, evt);
}
-TimedRemoteApp *timed_remote_app_alloc(void) {
- TimedRemoteApp *app = malloc(sizeof(TimedRemoteApp));
- memset(app, 0, sizeof(TimedRemoteApp));
-
- /* Open GUI record */
- app->gui = furi_record_open(RECORD_GUI);
-
- /* Allocate view dispatcher */
- app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
- view_dispatcher_set_navigation_event_callback(
- app->view_dispatcher, timed_remote_navigation_callback);
- view_dispatcher_set_custom_event_callback(app->view_dispatcher,
- timed_remote_custom_event_callback);
- view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui,
- ViewDispatcherTypeFullscreen);
-
- /* Allocate scene manager */
- app->scene_manager = scene_manager_alloc(&timed_remote_scene_handlers, app);
-
- /* Allocate views */
- app->submenu = submenu_alloc();
- view_dispatcher_add_view(app->view_dispatcher, TimedRemoteViewSubmenu,
- submenu_get_view(app->submenu));
-
- app->variable_item_list = variable_item_list_alloc();
- view_dispatcher_add_view(
- app->view_dispatcher, TimedRemoteViewVariableItemList,
- variable_item_list_get_view(app->variable_item_list));
-
- app->text_input = text_input_alloc();
- view_dispatcher_add_view(app->view_dispatcher, TimedRemoteViewTextInput,
- text_input_get_view(app->text_input));
-
- app->widget = widget_alloc();
- view_dispatcher_add_view(app->view_dispatcher, TimedRemoteViewWidget,
- widget_get_view(app->widget));
-
- app->popup = popup_alloc();
- view_dispatcher_add_view(app->view_dispatcher, TimedRemoteViewPopup,
- popup_get_view(app->popup));
-
- return app;
-}
+TimedRemoteApp *
+timed_remote_app_alloc(void)
+{
+ TimedRemoteApp *app = malloc(sizeof(TimedRemoteApp));
+ memset(app, 0, sizeof(TimedRemoteApp));
+
+ app->gui = furi_record_open(RECORD_GUI);
+
+ app->vd = view_dispatcher_alloc();
+ view_dispatcher_set_event_callback_context(app->vd, app);
+ view_dispatcher_set_navigation_event_callback(app->vd, nav_cb);
+ view_dispatcher_set_custom_event_callback(app->vd, evt_cb);
+ view_dispatcher_attach_to_gui(app->vd, app->gui,
+ ViewDispatcherTypeFullscreen);
+
+ app->sm = scene_manager_alloc(&scene_handlers, app);
+
+ app->submenu = submenu_alloc();
+ view_dispatcher_add_view(app->vd, ViewMenu,
+ submenu_get_view(app->submenu));
-void timed_remote_app_free(TimedRemoteApp *app) {
- /* Free timer if still running */
- if (app->timer) {
- furi_timer_stop(app->timer);
- furi_timer_free(app->timer);
- }
+ app->vlist = variable_item_list_alloc();
+ view_dispatcher_add_view(
+ app->vd, ViewList,
+ variable_item_list_get_view(app->vlist));
+
+ app->widget = widget_alloc();
+ view_dispatcher_add_view(app->vd, ViewRun,
+ widget_get_view(app->widget));
+
+ app->popup = popup_alloc();
+ view_dispatcher_add_view(app->vd, ViewPop,
+ popup_get_view(app->popup));
+
+ return app;
+}
- /* Free IR signal if allocated */
- if (app->ir_signal) {
- infrared_signal_free(app->ir_signal);
- }
+void
+timed_remote_app_free(TimedRemoteApp *app)
+{
+ if (app->timer)
+ {
+ furi_timer_stop(app->timer);
+ furi_timer_free(app->timer);
+ }
- /* Remove and free views */
- view_dispatcher_remove_view(app->view_dispatcher, TimedRemoteViewSubmenu);
- submenu_free(app->submenu);
+ if (app->ir)
+ {
+ infrared_signal_free(app->ir);
+ }
- view_dispatcher_remove_view(app->view_dispatcher,
- TimedRemoteViewVariableItemList);
- variable_item_list_free(app->variable_item_list);
+ view_dispatcher_remove_view(app->vd, ViewMenu);
+ submenu_free(app->submenu);
- view_dispatcher_remove_view(app->view_dispatcher, TimedRemoteViewTextInput);
- text_input_free(app->text_input);
+ view_dispatcher_remove_view(app->vd,
+ ViewList);
+ variable_item_list_free(app->vlist);
- view_dispatcher_remove_view(app->view_dispatcher, TimedRemoteViewWidget);
- widget_free(app->widget);
+ view_dispatcher_remove_view(app->vd, ViewRun);
+ widget_free(app->widget);
- view_dispatcher_remove_view(app->view_dispatcher, TimedRemoteViewPopup);
- popup_free(app->popup);
+ view_dispatcher_remove_view(app->vd, ViewPop);
+ popup_free(app->popup);
- /* Free scene manager */
- scene_manager_free(app->scene_manager);
+ scene_manager_free(app->sm);
- /* Free view dispatcher */
- view_dispatcher_free(app->view_dispatcher);
+ view_dispatcher_free(app->vd);
- /* Close GUI record */
- furi_record_close(RECORD_GUI);
+ furi_record_close(RECORD_GUI);
- free(app);
+ free(app);
}
-int32_t timed_remote_app(void *p) {
- UNUSED(p);
+int32_t
+timed_remote_app(void *p)
+{
+ UNUSED(p);
- TimedRemoteApp *app = timed_remote_app_alloc();
+ TimedRemoteApp *app = timed_remote_app_alloc();
- /* Start with file browser scene */
- scene_manager_next_scene(app->scene_manager, TimedRemoteSceneIrBrowse);
+ scene_manager_next_scene(app->sm, ScBrowse);
- /* Run event loop */
- view_dispatcher_run(app->view_dispatcher);
+ view_dispatcher_run(app->vd);
- /* Cleanup */
- timed_remote_app_free(app);
+ timed_remote_app_free(app);
- return 0;
+ return 0;
}
diff --git a/timed_remote.h b/timed_remote.h
@@ -4,69 +4,55 @@
#include <gui/gui.h>
#include <gui/modules/popup.h>
#include <gui/modules/submenu.h>
-#include <gui/modules/text_input.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/scene_manager.h>
#include <gui/view_dispatcher.h>
#include <lib/infrared/signal/infrared_signal.h>
-/* Timer mode enumeration */
-typedef enum {
- TimerModeCountdown,
- TimerModeScheduled,
-} TimerMode;
+typedef enum
+{
+ ModeDown,
+ ModeSched,
+} ModeId;
-/* View IDs for ViewDispatcher */
-typedef enum {
- TimedRemoteViewSubmenu,
- TimedRemoteViewVariableItemList,
- TimedRemoteViewTextInput,
- TimedRemoteViewWidget,
- TimedRemoteViewPopup,
-} TimedRemoteView;
+typedef enum
+{
+ ViewMenu,
+ ViewList,
+ ViewRun,
+ ViewPop,
+} ViewId;
-/* Maximum lengths */
#define SIGNAL_NAME_MAX_LEN 32
#define FILE_PATH_MAX_LEN 256
-/* Main application state */
-typedef struct {
- /* Core Flipper components */
- Gui *gui;
- ViewDispatcher *view_dispatcher;
- SceneManager *scene_manager;
+typedef struct
+{
+ Gui *gui;
+ ViewDispatcher *vd;
+ SceneManager *sm;
- /* Views */
- Submenu *submenu;
- VariableItemList *variable_item_list;
- TextInput *text_input;
- Widget *widget;
- Popup *popup;
+ Submenu *submenu;
+ VariableItemList *vlist;
+ Widget *widget;
+ Popup *popup;
- /* IR state */
- InfraredSignal *ir_signal;
- char signal_name[SIGNAL_NAME_MAX_LEN];
- char selected_file_path[FILE_PATH_MAX_LEN];
+ InfraredSignal *ir;
+ char sig[SIGNAL_NAME_MAX_LEN];
+ char file[FILE_PATH_MAX_LEN];
- /* Timer configuration */
- TimerMode timer_mode;
- uint8_t hours;
- uint8_t minutes;
- uint8_t seconds;
+ ModeId mode;
+ uint8_t h;
+ uint8_t m;
+ uint8_t s;
- /* Repeat options (Countdown mode only) */
- uint8_t repeat_count; /* 0 = off, 255 = unlimited, 1-99 = count */
- uint8_t repeats_remaining;
+ uint8_t repeat; /* 0 = off, 255 = unlimited, 1-99 = count */
+ uint8_t repeat_left;
- /* Timer runtime state */
- FuriTimer *timer;
- uint32_t seconds_remaining;
-
- /* Text input buffer */
- char text_input_buffer[SIGNAL_NAME_MAX_LEN];
+ FuriTimer *timer;
+ uint32_t left;
} TimedRemoteApp;
-/* App lifecycle */
TimedRemoteApp *timed_remote_app_alloc(void);
void timed_remote_app_free(TimedRemoteApp *app);