commit b42e66e49527342776a685b9e0e38bf5aab3ba6f Author: Jerry Aldrich Date: Sun Feb 1 12:47:50 2026 -0800 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdf1f3f --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## Lazy Light + +Simple Flipper App for controlling lights/fans in my apartment. + +Plz do not judge the C code, it was written quickly and without desire for beauty. diff --git a/app.png b/app.png new file mode 100644 index 0000000..a572c5b Binary files /dev/null and b/app.png differ diff --git a/application.fam b/application.fam new file mode 100644 index 0000000..e20597e --- /dev/null +++ b/application.fam @@ -0,0 +1,18 @@ +App( + appid="lazylight", + name="Lazy Light", + apptype=FlipperAppType.EXTERNAL, + entry_point="lazylight_main", + stack_size=4 * 1024, + requires=[ + "subghz", + "gui", + ], + order=10, + fap_icon="app.png", + fap_category="Sub-GHz", + fap_icon_assets="assets", + fap_description="Control lights/fans in Jerry's apartment", + fap_author="@jerryaldrichiii", + fap_version="0.1", +) diff --git a/lazylight.c b/lazylight.c new file mode 100644 index 0000000..7b5b164 --- /dev/null +++ b/lazylight.c @@ -0,0 +1,272 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +/* ===================== Views ===================== */ + +typedef enum { + ViewIndexMainMenu, + ViewIndexMasterBedroom, + ViewIndexGuestBedroom, + ViewIndexOffice, + ViewIndexCount, +} ViewIndex; + +/* ===================== Menu Indexes ===================== */ + +typedef enum { + MainMenuMasterBedroom, + MainMenuGuestBedroom, + MainMenuOffice, +} MainMenuIndex; + +typedef enum { + RoomActionToggleLight, + RoomActionFanOff, + RoomActionFanLow, + RoomActionFanMedium, + RoomActionFanHigh, +} RoomActionIndex; + +/* ===================== App Struct ===================== */ + +typedef struct { + ViewDispatcher* view_dispatcher; + Submenu* main_menu; + Submenu* master_menu; + Submenu* guest_menu; + Submenu* office_menu; + ViewIndex current_view; +} LazyLightApp; + +/* ===================== RF Transmit Helper ===================== */ + +static void lazylight_tx(uint32_t key) { + char* protocol = "CAME"; + + SubGhzEnvironment* env = subghz_environment_alloc(); + subghz_environment_set_protocol_registry( + env, (void*)&subghz_protocol_registry); + + SubGhzTransmitter* tx = subghz_transmitter_alloc_init(env, protocol); + + FlipperFormat* ff = flipper_format_string_alloc(); + + uint32_t bits = 12; + + uint8_t data[8] = {0}; + + data[7] = (uint8_t)(key & 0xFFU); + data[6] = (uint8_t)((key >> 8) & 0x0FU); + + flipper_format_insert_or_update_string_cstr(ff, "Protocol", "CAME"); + flipper_format_insert_or_update_uint32(ff, "Bit", &bits, 1); + flipper_format_insert_or_update_hex(ff, "Key", data, COUNT_OF(data)); + flipper_format_rewind(ff); + + furi_check( + subghz_transmitter_deserialize(tx, ff) == + SubGhzProtocolStatusOk); + + subghz_devices_init(); + const SubGhzDevice* dev = + subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + + subghz_devices_begin(dev); + subghz_devices_reset(dev); + subghz_devices_load_preset( + dev, FuriHalSubGhzPresetOok650Async, NULL); + subghz_devices_set_frequency(dev, 303900000); + + furi_hal_power_suppress_charge_enter(); + + if(subghz_devices_start_async_tx( + dev, subghz_transmitter_yield, tx)) { + while(!subghz_devices_is_async_complete_tx(dev)) { + furi_delay_ms(50); + } + subghz_devices_stop_async_tx(dev); + } + + furi_hal_power_suppress_charge_exit(); + + subghz_devices_sleep(dev); + subghz_devices_end(dev); + subghz_devices_deinit(); + + flipper_format_free(ff); + subghz_transmitter_free(tx); + subghz_environment_free(env); +} + +/* ===================== Navigation ===================== */ + +static bool navigation_callback(void* ctx) { + furi_check(ctx); + + LazyLightApp* app = ctx; + + if(app->current_view == ViewIndexMainMenu) { + view_dispatcher_stop(app->view_dispatcher); + } else { + app->current_view = ViewIndexMainMenu; + view_dispatcher_switch_to_view( + app->view_dispatcher, ViewIndexMainMenu); + } + return true; +} + +/* ===================== Menu Callbacks ===================== */ + +static void main_menu_cb(void* ctx, uint32_t index) { + LazyLightApp* app = ctx; + app->current_view = index + 1; + view_dispatcher_switch_to_view( + app->view_dispatcher, app->current_view); +} + +static void master_cb(void* ctx, uint32_t index) { + UNUSED(ctx); + if(index == RoomActionToggleLight) lazylight_tx(0x1F6); + if(index == RoomActionFanOff) lazylight_tx(0x1F5); + if(index == RoomActionFanLow) lazylight_tx(0x1FD); + if(index == RoomActionFanMedium) lazylight_tx(0x1FB); + if(index == RoomActionFanHigh) lazylight_tx(0x1F7); +} + +static void guest_cb(void* ctx, uint32_t index) { + UNUSED(ctx); + if(index == RoomActionToggleLight) lazylight_tx(0x66); + if(index == RoomActionFanOff) lazylight_tx(0x65); + if(index == RoomActionFanLow) lazylight_tx(0x6D); + if(index == RoomActionFanMedium) lazylight_tx(0x6B); + if(index == RoomActionFanHigh) lazylight_tx(0x67); +} + +static void office_cb(void* ctx, uint32_t index) { + UNUSED(ctx); + if(index == RoomActionToggleLight) lazylight_tx(0xE6); + if(index == RoomActionFanOff) lazylight_tx(0xE5); + if(index == RoomActionFanLow) lazylight_tx(0xED); + if(index == RoomActionFanMedium) lazylight_tx(0xEB); + if(index == RoomActionFanHigh) lazylight_tx(0xE7); +} + +/* ===================== App Setup ===================== */ + +static LazyLightApp* app_alloc() { + LazyLightApp* app = malloc(sizeof(LazyLightApp)); + Gui* gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_attach_to_gui( + app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, navigation_callback); + view_dispatcher_set_event_callback_context( + app->view_dispatcher, app); + + /* Main Menu */ + app->main_menu = submenu_alloc(); + submenu_add_item(app->main_menu, "Master Bedroom", + MainMenuMasterBedroom, main_menu_cb, app); + submenu_add_item(app->main_menu, "Guest Bedroom", + MainMenuGuestBedroom, main_menu_cb, app); + submenu_add_item(app->main_menu, "Office", + MainMenuOffice, main_menu_cb, app); + + /* Master Bedroom */ + app->master_menu = submenu_alloc(); + submenu_add_item(app->master_menu, "Toggle Light", + RoomActionToggleLight, master_cb, app); + submenu_add_item(app->master_menu, "Turn Fan Off", + RoomActionFanOff, master_cb, app); + submenu_add_item(app->master_menu, "Set Fan Low", + RoomActionFanLow, master_cb, app); + submenu_add_item(app->master_menu, "Set Fan Medium", + RoomActionFanMedium, master_cb, app); + submenu_add_item(app->master_menu, "Set Fan High", + RoomActionFanHigh, master_cb, app); + + /* Guest Bedroom */ + app->guest_menu = submenu_alloc(); + submenu_add_item(app->guest_menu, "Toggle Light", + RoomActionToggleLight, guest_cb, app); + submenu_add_item(app->guest_menu, "Turn Fan Off", + RoomActionFanOff, guest_cb, app); + submenu_add_item(app->guest_menu, "Set Fan Low", + RoomActionFanLow, guest_cb, app); + submenu_add_item(app->guest_menu, "Set Fan Medium", + RoomActionFanMedium, guest_cb, app); + submenu_add_item(app->guest_menu, "Set Fan High", + RoomActionFanHigh, guest_cb, app); + + /* Office */ + app->office_menu = submenu_alloc(); + submenu_add_item(app->office_menu, "Toggle Light", + RoomActionToggleLight, office_cb, app); + submenu_add_item(app->office_menu, "Turn Fan Off", + RoomActionFanOff, office_cb, app); + submenu_add_item(app->office_menu, "Set Fan Low", + RoomActionFanLow, office_cb, app); + submenu_add_item(app->office_menu, "Set Fan Medium", + RoomActionFanMedium, office_cb, app); + submenu_add_item(app->office_menu, "Set Fan High", + RoomActionFanHigh, office_cb, app); + + view_dispatcher_add_view( + app->view_dispatcher, + ViewIndexMainMenu, + submenu_get_view(app->main_menu)); + view_dispatcher_add_view( + app->view_dispatcher, + ViewIndexMasterBedroom, + submenu_get_view(app->master_menu)); + view_dispatcher_add_view( + app->view_dispatcher, + ViewIndexGuestBedroom, + submenu_get_view(app->guest_menu)); + view_dispatcher_add_view( + app->view_dispatcher, + ViewIndexOffice, + submenu_get_view(app->office_menu)); + + app->current_view = ViewIndexMainMenu; + return app; +} + +static void app_free(LazyLightApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, ViewIndexMainMenu); + view_dispatcher_remove_view(app->view_dispatcher, ViewIndexMasterBedroom); + view_dispatcher_remove_view(app->view_dispatcher, ViewIndexGuestBedroom); + view_dispatcher_remove_view(app->view_dispatcher, ViewIndexOffice); + + view_dispatcher_free(app->view_dispatcher); + submenu_free(app->main_menu); + submenu_free(app->master_menu); + submenu_free(app->guest_menu); + submenu_free(app->office_menu); + furi_record_close(RECORD_GUI); + free(app); +} + +/* ===================== Entry ===================== */ + +int32_t lazylight_main(void* arg) { + UNUSED(arg); + + LazyLightApp* app = app_alloc(); + view_dispatcher_switch_to_view( + app->view_dispatcher, ViewIndexMainMenu); + view_dispatcher_run(app->view_dispatcher); + app_free(app); + + return 0; +} +