diff --git a/base_pack/camera_suite/application.fam b/base_pack/camera_suite/application.fam index 09d549fc288..ab0d87b1c61 100644 --- a/base_pack/camera_suite/application.fam +++ b/base_pack/camera_suite/application.fam @@ -7,9 +7,7 @@ App( fap_category="GPIO", fap_description="A camera suite application for the Flipper Zero ESP32-CAM module.", fap_icon="icons/camera_suite.png", - fap_icon_assets="assets", - fap_libs=["assets"], - fap_version="1.4", + fap_version="1.7", fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam", name="[ESP32] Camera Suite", order=1, diff --git a/base_pack/camera_suite/assets/.gitkeep b/base_pack/camera_suite/assets/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/base_pack/camera_suite/assets/DolphinCommon_56x48.png b/base_pack/camera_suite/assets/DolphinCommon_56x48.png deleted file mode 100644 index 089aaed8350..00000000000 Binary files a/base_pack/camera_suite/assets/DolphinCommon_56x48.png and /dev/null differ diff --git a/base_pack/camera_suite/camera_suite.c b/base_pack/camera_suite/camera_suite.c index a78d8d27bba..c8289d72eaf 100644 --- a/base_pack/camera_suite/camera_suite.c +++ b/base_pack/camera_suite/camera_suite.c @@ -43,16 +43,18 @@ CameraSuite* camera_suite_app_alloc() { app->view_dispatcher, camera_suite_custom_event_callback); app->submenu = submenu_alloc(); - // Set defaults, in case no config loaded - app->orientation = 0; // Orientation is "portrait", zero degrees by default. - app->dither = 0; // Dither algorithm is "Floyd Steinberg" by default. - app->flash = 1; // Flash is enabled by default. + // Set app default settings values. app->haptic = 1; // Haptic is enabled by default app->jpeg = 0; // Save JPEG to ESP32-CAM sd-card is disabled by default. app->speaker = 1; // Speaker is enabled by default app->led = 1; // LED is enabled by default - // Load configs + // Set cam default settings values. + app->orientation = 0; // Orientation is "portrait", zero degrees by default. + app->dither = 0; // Dither algorithm is "Floyd Steinberg" by default. + app->flash = 1; // Flash is enabled by default. + + // Load configs if available (overrides defaults). camera_suite_read_settings(app); view_dispatcher_add_view( @@ -81,7 +83,11 @@ CameraSuite* camera_suite_app_alloc() { app->variable_item_list = variable_item_list_alloc(); view_dispatcher_add_view( app->view_dispatcher, - CameraSuiteViewIdSettings, + CameraSuiteViewIdAppSettings, + variable_item_list_get_view(app->variable_item_list)); + view_dispatcher_add_view( + app->view_dispatcher, + CameraSuiteViewIdCamSettings, variable_item_list_get_view(app->variable_item_list)); //End Scene Additions @@ -100,7 +106,8 @@ void camera_suite_app_free(CameraSuite* app) { view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdMenu); view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdCamera); view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdGuide); - view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdSettings); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdAppSettings); + view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdCamSettings); submenu_free(app->submenu); view_dispatcher_free(app->view_dispatcher); @@ -110,6 +117,7 @@ void camera_suite_app_free(CameraSuite* app) { camera_suite_view_start_free(app->camera_suite_view_start); camera_suite_view_camera_free(app->camera_suite_view_camera); camera_suite_view_guide_free(app->camera_suite_view_guide); + button_menu_free(app->button_menu); variable_item_list_free(app->variable_item_list); diff --git a/base_pack/camera_suite/camera_suite.h b/base_pack/camera_suite/camera_suite.h index bbf895cdfcd..47cccf963dc 100644 --- a/base_pack/camera_suite/camera_suite.h +++ b/base_pack/camera_suite/camera_suite.h @@ -45,7 +45,8 @@ typedef enum { CameraSuiteViewIdMenu, CameraSuiteViewIdCamera, CameraSuiteViewIdGuide, - CameraSuiteViewIdSettings, + CameraSuiteViewIdAppSettings, + CameraSuiteViewIdCamSettings, } CameraSuiteViewId; typedef enum { diff --git a/base_pack/camera_suite/docs/CHANGELOG.md b/base_pack/camera_suite/docs/CHANGELOG.md index e97e949ce3a..2638599e02b 100644 --- a/base_pack/camera_suite/docs/CHANGELOG.md +++ b/base_pack/camera_suite/docs/CHANGELOG.md @@ -1,8 +1,28 @@ -## Roadmap +# Roadmap - Store images to onboard ESP32-CAM SD card (partially completed, #24). - Camera preview GUI overlay (#21). - Full screen 90 degree and 270 degree fill (#6). +- WiFi streaming/connection support (#35). + +## v1.7 + +- Add support for new Flipper Zero Firmware UART updates. +- Remove staged WiFi streaming/connection support for now. Until I can fully test. + +## v1.6 + +- Add new splash/start screen. +- Add new module not connected notification + pinout guide in-app. +- Update README with a new "Special Thanks" section. +- Update README "Contributions" section regarding firmware development. +- Separate settings into two views: app and cam settings. +- General code improvements and cleanup. +- Stage new scene for WiFi streaming/connection support (#35). + +## v1.5 + +- Remove usage of image no longer found in the Flipper Zero firmware build. ## v1.4 @@ -12,9 +32,6 @@ - Improve Firmware flashing utility code. - Improve GitHub actions code. - Look to mitigate issue "Mirrored Image" #27. - -## v1.3.1 (patch) - - Addressed new linting issue with "ufbt" tools. ## v1.3 @@ -36,25 +53,25 @@ - Update documentation to reflect changes. - Update firmware with new dithering options set. - Update firmware with new flash support. -- Update repo to reflect https://github.com/CodyTolene/Flipper-Zero-Development-Toolkit for easier tooling. +- Update repo to reflect for easier tooling. ## v1.1 - Support and picture stabilization for all camera orientations (0 degree, 90 degree, 180 degree, and 270 degree). - Rename "Scene 1" to "Camera". No UX changes there. - Clean up unused "Scene 2". This was inaccessible to users previously and unused. -- Add new dithering variations (requires the latest firmware installation, see here for the installation guide https://github.com/CodyTolene/Flipper-Zero-Camera-Suite#firmware-installation): +- Add new dithering variations (requires the latest firmware installation, see here for the installation guide ): - "Jarvis Judice Ninke" dithering option - "Stucki" dithering option. - "Floyd-Steinberg" dithering option. - Cycle through the dithering options with the center button on the Flipper Zero. -- Resolves issue https://github.com/CodyTolene/Flipper-Zero-Camera-Suite/issues/7 -- Resolves issue https://github.com/CodyTolene/Flipper-Zero-Camera-Suite/pull/17 +- Resolves issue +- Resolves issue ## v1.0 -- Builds upon Z4urce's software found here (updated 6 months ago): https://github.com/Z4urce/flipperzero-camera -- Utilizes the superb C boilerplate examples laid out by leedave (updated last month): https://github.com/leedave/flipper-zero-fap-boilerplate +- Builds upon Z4urce's software found here (updated 6 months ago): +- Utilizes the superb C boilerplate examples laid out by leedave (updated last month): - Builds upon the "Camera" software into the new "Camera Suite" application with new usage: - Add a scene for a guide. - Add a scene for settings. diff --git a/base_pack/camera_suite/docs/README.md b/base_pack/camera_suite/docs/README.md index 8f5204c99a1..8abef6dba6e 100644 --- a/base_pack/camera_suite/docs/README.md +++ b/base_pack/camera_suite/docs/README.md @@ -10,9 +10,9 @@ Firmware is needed for the ESP32-CAM module, see here for more information: http Button mappings: -**Up** = Contrast Up +**Up** = Contrast Up. -**Down** = Contrast Down +**Down** = Contrast Down. **Left** = Toggle invert. @@ -30,8 +30,8 @@ Settings: **Dithering Type** Change between the Cycle Floyd–Steinberg, Jarvis-Judice-Ninke, and Stucki dithering types. -**Haptic FX** = Toggle haptic feedback on/off. +**Haptic Effects** = Toggle haptic feedback on/off. -**Sound FX** = Toggle sound effects on/off. +**Sound Effects** = Toggle sound effects on/off. -**LED FX** = Toggle LED effects on/off. +**LED Effects** = Toggle LED effects on/off. diff --git a/base_pack/camera_suite/scenes/camera_suite_scene_app_settings.c b/base_pack/camera_suite/scenes/camera_suite_scene_app_settings.c new file mode 100644 index 00000000000..dce74772d71 --- /dev/null +++ b/base_pack/camera_suite/scenes/camera_suite_scene_app_settings.c @@ -0,0 +1,111 @@ +#include "../camera_suite.h" +#include + +const char* const haptic_text[2] = { + "OFF", + "ON", +}; + +const uint32_t haptic_value[2] = { + CameraSuiteHapticOff, + CameraSuiteHapticOn, +}; + +const char* const speaker_text[2] = { + "OFF", + "ON", +}; + +const uint32_t speaker_value[2] = { + CameraSuiteSpeakerOff, + CameraSuiteSpeakerOn, +}; + +const char* const led_text[2] = { + "OFF", + "ON", +}; + +const uint32_t led_value[2] = { + CameraSuiteLedOff, + CameraSuiteLedOn, +}; + +static void camera_suite_scene_app_settings_set_haptic(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, haptic_text[index]); + app->haptic = haptic_value[index]; +} + +static void camera_suite_scene_app_settings_set_speaker(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, speaker_text[index]); + app->speaker = speaker_value[index]; +} + +static void camera_suite_scene_app_settings_set_led(VariableItem* item) { + CameraSuite* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, led_text[index]); + app->led = led_value[index]; +} + +void camera_suite_scene_app_settings_submenu_callback(void* context, uint32_t index) { + CameraSuite* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void camera_suite_scene_app_settings_on_enter(void* context) { + CameraSuite* app = context; + VariableItem* item; + uint8_t value_index; + + // Haptic Effects ON/OFF + item = variable_item_list_add( + app->variable_item_list, + "Haptic Effects:", + 2, + camera_suite_scene_app_settings_set_haptic, + app); + value_index = value_index_uint32(app->haptic, haptic_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, haptic_text[value_index]); + + // Sound Effects ON/OFF + item = variable_item_list_add( + app->variable_item_list, + "Sound Effects:", + 2, + camera_suite_scene_app_settings_set_speaker, + app); + value_index = value_index_uint32(app->speaker, speaker_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + + // LED Effects ON/OFF + item = variable_item_list_add( + app->variable_item_list, "LED Effects:", 2, camera_suite_scene_app_settings_set_led, app); + value_index = value_index_uint32(app->led, led_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, led_text[value_index]); + + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdAppSettings); +} + +bool camera_suite_scene_app_settings_on_event(void* context, SceneManagerEvent event) { + CameraSuite* app = context; + UNUSED(app); + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + } + return consumed; +} + +void camera_suite_scene_app_settings_on_exit(void* context) { + CameraSuite* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} diff --git a/base_pack/camera_suite/scenes/camera_suite_scene_settings.c b/base_pack/camera_suite/scenes/camera_suite_scene_cam_settings.c similarity index 55% rename from base_pack/camera_suite/scenes/camera_suite_scene_settings.c rename to base_pack/camera_suite/scenes/camera_suite_scene_cam_settings.c index 3fad7ee79d9..47861eb78cd 100644 --- a/base_pack/camera_suite/scenes/camera_suite_scene_settings.c +++ b/base_pack/camera_suite/scenes/camera_suite_scene_cam_settings.c @@ -49,37 +49,7 @@ const uint32_t jpeg_value[2] = { CameraSuiteJpegOn, }; -const char* const haptic_text[2] = { - "OFF", - "ON", -}; - -const uint32_t haptic_value[2] = { - CameraSuiteHapticOff, - CameraSuiteHapticOn, -}; - -const char* const speaker_text[2] = { - "OFF", - "ON", -}; - -const uint32_t speaker_value[2] = { - CameraSuiteSpeakerOff, - CameraSuiteSpeakerOn, -}; - -const char* const led_text[2] = { - "OFF", - "ON", -}; - -const uint32_t led_value[2] = { - CameraSuiteLedOff, - CameraSuiteLedOn, -}; - -static void camera_suite_scene_settings_set_camera_orientation(VariableItem* item) { +static void camera_suite_scene_cam_settings_set_camera_orientation(VariableItem* item) { CameraSuite* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -87,7 +57,7 @@ static void camera_suite_scene_settings_set_camera_orientation(VariableItem* ite app->orientation = orientation_value[index]; } -static void camera_suite_scene_settings_set_camera_dither(VariableItem* item) { +static void camera_suite_scene_cam_settings_set_camera_dither(VariableItem* item) { CameraSuite* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -95,7 +65,7 @@ static void camera_suite_scene_settings_set_camera_dither(VariableItem* item) { app->dither = dither_value[index]; } -static void camera_suite_scene_settings_set_flash(VariableItem* item) { +static void camera_suite_scene_cam_settings_set_flash(VariableItem* item) { CameraSuite* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -103,7 +73,7 @@ static void camera_suite_scene_settings_set_flash(VariableItem* item) { app->flash = flash_value[index]; } -static void camera_suite_scene_settings_set_jpeg(VariableItem* item) { +static void camera_suite_scene_cam_settings_set_jpeg(VariableItem* item) { CameraSuite* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -111,34 +81,12 @@ static void camera_suite_scene_settings_set_jpeg(VariableItem* item) { app->jpeg = jpeg_value[index]; } -static void camera_suite_scene_settings_set_haptic(VariableItem* item) { - CameraSuite* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, haptic_text[index]); - app->haptic = haptic_value[index]; -} - -static void camera_suite_scene_settings_set_speaker(VariableItem* item) { - CameraSuite* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, speaker_text[index]); - app->speaker = speaker_value[index]; -} - -static void camera_suite_scene_settings_set_led(VariableItem* item) { - CameraSuite* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, led_text[index]); - app->led = led_value[index]; -} - -void camera_suite_scene_settings_submenu_callback(void* context, uint32_t index) { +void camera_suite_scene_cam_settings_submenu_callback(void* context, uint32_t index) { CameraSuite* app = context; view_dispatcher_send_custom_event(app->view_dispatcher, index); } -void camera_suite_scene_settings_on_enter(void* context) { +void camera_suite_scene_cam_settings_on_enter(void* context) { CameraSuite* app = context; VariableItem* item; uint8_t value_index; @@ -148,7 +96,7 @@ void camera_suite_scene_settings_on_enter(void* context) { app->variable_item_list, "Orientation:", 4, - camera_suite_scene_settings_set_camera_orientation, + camera_suite_scene_cam_settings_set_camera_orientation, app); value_index = value_index_uint32(app->orientation, orientation_value, 4); variable_item_set_current_value_index(item, value_index); @@ -159,7 +107,7 @@ void camera_suite_scene_settings_on_enter(void* context) { app->variable_item_list, "Dithering Type:", 3, - camera_suite_scene_settings_set_camera_dither, + camera_suite_scene_cam_settings_set_camera_dither, app); value_index = value_index_uint32(app->dither, dither_value, 3); variable_item_set_current_value_index(item, value_index); @@ -167,7 +115,7 @@ void camera_suite_scene_settings_on_enter(void* context) { // Flash ON/OFF item = variable_item_list_add( - app->variable_item_list, "Flash:", 2, camera_suite_scene_settings_set_flash, app); + app->variable_item_list, "Flash:", 2, camera_suite_scene_cam_settings_set_flash, app); value_index = value_index_uint32(app->flash, flash_value, 2); variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, flash_text[value_index]); @@ -179,38 +127,17 @@ void camera_suite_scene_settings_on_enter(void* context) { // app->variable_item_list, // "Save JPEG to ext sdcard:", // 2, - // camera_suite_scene_settings_set_jpeg, + // camera_suite_scene_cam_settings_set_jpeg, // app); // value_index = value_index_uint32(app->jpeg, jpeg_value, 2); // variable_item_set_current_value_index(item, value_index); // variable_item_set_current_value_text(item, jpeg_text[value_index]); - UNUSED(camera_suite_scene_settings_set_jpeg); + UNUSED(camera_suite_scene_cam_settings_set_jpeg); - // Haptic FX ON/OFF - item = variable_item_list_add( - app->variable_item_list, "Haptic FX:", 2, camera_suite_scene_settings_set_haptic, app); - value_index = value_index_uint32(app->haptic, haptic_value, 2); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, haptic_text[value_index]); - - // Sound FX ON/OFF - item = variable_item_list_add( - app->variable_item_list, "Sound FX:", 2, camera_suite_scene_settings_set_speaker, app); - value_index = value_index_uint32(app->speaker, speaker_value, 2); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, speaker_text[value_index]); - - // LED FX ON/OFF - item = variable_item_list_add( - app->variable_item_list, "LED FX:", 2, camera_suite_scene_settings_set_led, app); - value_index = value_index_uint32(app->led, led_value, 2); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, led_text[value_index]); - - view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdSettings); + view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdCamSettings); } -bool camera_suite_scene_settings_on_event(void* context, SceneManagerEvent event) { +bool camera_suite_scene_cam_settings_on_event(void* context, SceneManagerEvent event) { CameraSuite* app = context; UNUSED(app); bool consumed = false; @@ -219,8 +146,8 @@ bool camera_suite_scene_settings_on_event(void* context, SceneManagerEvent event return consumed; } -void camera_suite_scene_settings_on_exit(void* context) { +void camera_suite_scene_cam_settings_on_exit(void* context) { CameraSuite* app = context; variable_item_list_set_selected_item(app->variable_item_list, 0); variable_item_list_reset(app->variable_item_list); -} \ No newline at end of file +} diff --git a/base_pack/camera_suite/scenes/camera_suite_scene_config.h b/base_pack/camera_suite/scenes/camera_suite_scene_config.h index 2cb9245efcc..a9f0e057aeb 100644 --- a/base_pack/camera_suite/scenes/camera_suite_scene_config.h +++ b/base_pack/camera_suite/scenes/camera_suite_scene_config.h @@ -2,4 +2,5 @@ ADD_SCENE(camera_suite, start, Start) ADD_SCENE(camera_suite, menu, Menu) ADD_SCENE(camera_suite, camera, Camera) ADD_SCENE(camera_suite, guide, Guide) -ADD_SCENE(camera_suite, settings, Settings) \ No newline at end of file +ADD_SCENE(camera_suite, app_settings, AppSettings) +ADD_SCENE(camera_suite, cam_settings, CamSettings) diff --git a/base_pack/camera_suite/scenes/camera_suite_scene_menu.c b/base_pack/camera_suite/scenes/camera_suite_scene_menu.c index ae37e11b6a8..c6c88038bb4 100644 --- a/base_pack/camera_suite/scenes/camera_suite_scene_menu.c +++ b/base_pack/camera_suite/scenes/camera_suite_scene_menu.c @@ -3,10 +3,12 @@ enum SubmenuIndex { /** Camera. */ SubmenuIndexSceneCamera = 10, + /** Cam settings menu. */ + SubmenuIndexCamSettings, + /** App settings menu. */ + SubmenuIndexAppSettings, /** Guide/how-to. */ SubmenuIndexGuide, - /** Settings menu. */ - SubmenuIndexSettings, }; void camera_suite_scene_menu_submenu_callback(void* context, uint32_t index) { @@ -19,16 +21,29 @@ void camera_suite_scene_menu_on_enter(void* context) { submenu_add_item( app->submenu, - "Open Camera", + "Stream Camera to Screen", SubmenuIndexSceneCamera, camera_suite_scene_menu_submenu_callback, app); + + submenu_add_item( + app->submenu, + "Camera Settings", + SubmenuIndexCamSettings, + camera_suite_scene_menu_submenu_callback, + app); + submenu_add_item( - app->submenu, "Guide", SubmenuIndexGuide, camera_suite_scene_menu_submenu_callback, app); + app->submenu, + "Application Settings", + SubmenuIndexAppSettings, + camera_suite_scene_menu_submenu_callback, + app); + submenu_add_item( app->submenu, - "Settings", - SubmenuIndexSettings, + "Camera Suite Guide", + SubmenuIndexGuide, camera_suite_scene_menu_submenu_callback, app); @@ -52,16 +67,21 @@ bool camera_suite_scene_menu_on_event(void* context, SceneManagerEvent event) { app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneCamera); scene_manager_next_scene(app->scene_manager, CameraSuiteSceneCamera); return true; + } else if(event.event == SubmenuIndexAppSettings) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexAppSettings); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneAppSettings); + return true; + } else if(event.event == SubmenuIndexCamSettings) { + scene_manager_set_scene_state( + app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexCamSettings); + scene_manager_next_scene(app->scene_manager, CameraSuiteSceneCamSettings); + return true; } else if(event.event == SubmenuIndexGuide) { scene_manager_set_scene_state( app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexGuide); scene_manager_next_scene(app->scene_manager, CameraSuiteSceneGuide); return true; - } else if(event.event == SubmenuIndexSettings) { - scene_manager_set_scene_state( - app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSettings); - scene_manager_next_scene(app->scene_manager, CameraSuiteSceneSettings); - return true; } } return false; diff --git a/base_pack/camera_suite/screenshots/camera.png b/base_pack/camera_suite/screenshots/camera.png new file mode 100644 index 00000000000..69950716619 Binary files /dev/null and b/base_pack/camera_suite/screenshots/camera.png differ diff --git a/base_pack/camera_suite/screenshots/guide.png b/base_pack/camera_suite/screenshots/guide.png index 87ed51218cb..f667c2073ae 100644 Binary files a/base_pack/camera_suite/screenshots/guide.png and b/base_pack/camera_suite/screenshots/guide.png differ diff --git a/base_pack/camera_suite/screenshots/guide_connect.png b/base_pack/camera_suite/screenshots/guide_connect.png new file mode 100644 index 00000000000..e87683b6d53 Binary files /dev/null and b/base_pack/camera_suite/screenshots/guide_connect.png differ diff --git a/base_pack/camera_suite/screenshots/main_menu.png b/base_pack/camera_suite/screenshots/main_menu.png index 3ae802cc2d3..40dba30f4e8 100644 Binary files a/base_pack/camera_suite/screenshots/main_menu.png and b/base_pack/camera_suite/screenshots/main_menu.png differ diff --git a/base_pack/camera_suite/screenshots/settings.png b/base_pack/camera_suite/screenshots/settings.png deleted file mode 100644 index 23d891234b6..00000000000 Binary files a/base_pack/camera_suite/screenshots/settings.png and /dev/null differ diff --git a/base_pack/camera_suite/screenshots/settings_app.png b/base_pack/camera_suite/screenshots/settings_app.png new file mode 100644 index 00000000000..f085e869fb4 Binary files /dev/null and b/base_pack/camera_suite/screenshots/settings_app.png differ diff --git a/base_pack/camera_suite/screenshots/settings_camera.png b/base_pack/camera_suite/screenshots/settings_camera.png new file mode 100644 index 00000000000..076c500f0ef Binary files /dev/null and b/base_pack/camera_suite/screenshots/settings_camera.png differ diff --git a/base_pack/camera_suite/screenshots/start_screen.png b/base_pack/camera_suite/screenshots/start_screen.png deleted file mode 100644 index 6fe690c5841..00000000000 Binary files a/base_pack/camera_suite/screenshots/start_screen.png and /dev/null differ diff --git a/base_pack/camera_suite/views/camera_suite_view_camera.c b/base_pack/camera_suite/views/camera_suite_view_camera.c index 3b6a6a151e0..3f936aeeec2 100644 --- a/base_pack/camera_suite/views/camera_suite_view_camera.c +++ b/base_pack/camera_suite/views/camera_suite_view_camera.c @@ -59,15 +59,100 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) { } } - // Draw the guide if the camera is not initialized. + // Draw the pinout guide if the camera is not initialized. if(!uartDumpModel->is_initialized) { - canvas_draw_icon(canvas, 74, 16, &I_DolphinCommon_56x48); + // Clear the screen. + canvas_clear(canvas); + + // Draw the ESP32-CAM module. + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 47, 50, "ESP32"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 52, 58, "CAM"); + canvas_draw_dot(canvas, 84, 3); + canvas_draw_box(canvas, 50, 35, 23, 7); + canvas_draw_circle(canvas, 42, 12, 1); + canvas_draw_circle(canvas, 42, 16, 1); + canvas_draw_circle(canvas, 42, 20, 1); + canvas_draw_circle(canvas, 42, 24, 1); + canvas_draw_circle(canvas, 42, 28, 1); + canvas_draw_circle(canvas, 42, 32, 1); + canvas_draw_circle(canvas, 42, 36, 1); + canvas_draw_circle(canvas, 42, 8, 1); + canvas_draw_circle(canvas, 59, 15, 1); + canvas_draw_circle(canvas, 61, 17, 5); + canvas_draw_circle(canvas, 61, 17, 9); + canvas_draw_circle(canvas, 80, 12, 1); + canvas_draw_circle(canvas, 80, 16, 1); + canvas_draw_circle(canvas, 80, 20, 1); + canvas_draw_circle(canvas, 80, 24, 1); + canvas_draw_circle(canvas, 80, 28, 1); + canvas_draw_circle(canvas, 80, 32, 1); + canvas_draw_circle(canvas, 80, 36, 1); + canvas_draw_circle(canvas, 80, 42, 1); + canvas_draw_circle(canvas, 80, 8, 1); + canvas_draw_line(canvas, 38, 4, 38, 58); + canvas_draw_line(canvas, 39, 3, 83, 3); + canvas_draw_line(canvas, 40, 2, 84, 2); + canvas_draw_line(canvas, 48, 4, 74, 4); + canvas_draw_line(canvas, 48, 5, 48, 26); + canvas_draw_line(canvas, 55, 27, 49, 27); + canvas_draw_line(canvas, 56, 25, 56, 36); + canvas_draw_line(canvas, 64, 21, 63, 21); + canvas_draw_line(canvas, 65, 15, 65, 17); + canvas_draw_line(canvas, 66, 15, 64, 18); + canvas_draw_line(canvas, 66, 16, 64, 19); + canvas_draw_line(canvas, 66, 18, 60, 21); + canvas_draw_line(canvas, 66, 19, 61, 21); + canvas_draw_line(canvas, 66, 25, 66, 36); + canvas_draw_line(canvas, 73, 27, 67, 27); + canvas_draw_line(canvas, 74, 5, 74, 26); + canvas_draw_line(canvas, 75, 4, 75, 25); + canvas_draw_line(canvas, 83, 59, 39, 59); + canvas_draw_line(canvas, 84, 4, 84, 58); + canvas_draw_line(canvas, 85, 2, 85, 57); + canvas_draw_frame(canvas, 78, 40, 5, 5); + + // Draw the pinout lines. + canvas_draw_line(canvas, 39, 8, 21, 8); + canvas_draw_line(canvas, 87, 24, 83, 24); + canvas_draw_line(canvas, 87, 32, 83, 32); + canvas_draw_line(canvas, 88, 23, 88, 13); + canvas_draw_line(canvas, 88, 33, 88, 43); + canvas_draw_line(canvas, 89, 12, 126, 12); + canvas_draw_line(canvas, 126, 28, 83, 28); + canvas_draw_line(canvas, 126, 44, 89, 44); + + // Draw the pinout labels. + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 91, 11, "VCC-3V"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 91, 27, "U0R-TX"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 91, 43, "U0T-RX"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 12, "GND"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 12, 21, "-GND"); + + // Draw the "Please Connect Module!" text. + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 40, "Please"); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 8, 12, "Connect the ESP32-CAM"); - canvas_draw_str(canvas, 20, 24, "VCC - 3V3"); - canvas_draw_str(canvas, 20, 34, "GND - GND"); - canvas_draw_str(canvas, 20, 44, "U0R - TX"); - canvas_draw_str(canvas, 20, 54, "U0T - RX"); + canvas_draw_str(canvas, 2, 49, "Connect"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 58, "Module!"); + + // Draw the "Back" text and button logo. + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 92, 57, "Back"); + canvas_draw_line(canvas, 116, 49, 116, 53); + canvas_draw_line(canvas, 115, 50, 115, 52); + canvas_draw_dot(canvas, 114, 51); + canvas_draw_line(canvas, 117, 51, 121, 51); + canvas_draw_line(canvas, 122, 52, 123, 53); + canvas_draw_line(canvas, 123, 54, 122, 55); + canvas_draw_line(canvas, 121, 56, 117, 56); } } @@ -94,10 +179,20 @@ static void save_image_to_flipper_sd_card(void* model) { FuriString* file_name = furi_string_alloc(); // Get the current date and time. + + // Not supported in "Release" F0 build. + // TODO: Remove when DateTime is supported in "Release" F0 build. + // FuriHalRtcDateTime datetime = {0}; + + // Only supported in "RC" & "Dev" builds. + // TODO: Uncomment when DateTime is supported in "Release" F0 build. DateTime datetime = {0}; + + // TODO: Uncomment when DateTime is supported in "Release" F0 build. furi_hal_rtc_get_datetime(&datetime); - // Create the file name. + // Create the file name using DateTime. + // TODO: Uncomment when DateTime is supported in "Release" F0 build. furi_string_printf( file_name, EXT_PATH("DCIM/%.4d%.2d%.2d-%.2d%.2d%.2d.bmp"), @@ -108,6 +203,10 @@ static void save_image_to_flipper_sd_card(void* model) { datetime.minute, datetime.second); + // Just use a random number for now instead of DateTime. + // int random_number = rand(); + // furi_string_printf(file_name, EXT_PATH("DCIM/%d.bmp"), random_number); + // Open the file for writing. If the file does not exist (it shouldn't), // create it. bool result = @@ -332,6 +431,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { } } } + return false; } @@ -382,12 +482,10 @@ static void // Cast `context` to `CameraSuiteViewCamera*` and store it in `instance`. CameraSuiteViewCamera* instance = context; - // If `uartIrqEvent` is `UartIrqEventRXNE`, send the data to the - // `rx_stream` and set the `WorkerEventRx` flag. if(event == FuriHalSerialRxEventData) { uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(instance->rx_stream, &data, 1, 0); - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), WorkerEventRx); + furi_stream_buffer_send(instance->camera_rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(instance->camera_worker_thread), WorkerEventRx); } } @@ -439,31 +537,39 @@ static void process_ringbuffer(UartDumpModel* model, uint8_t const byte) { } } -static int32_t camera_worker(void* context) { +static int32_t camera_suite_camera_worker(void* context) { furi_assert(context); CameraSuiteViewCamera* instance = context; while(1) { + // Wait for any event on the worker thread. uint32_t events = - furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever); + furi_thread_flags_wait(CAMERA_WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever); + + // Check if an error occurred. furi_check((events & FuriFlagError) == 0); + // Check if the thread should stop. if(events & WorkerEventStop) { break; } else if(events & WorkerEventRx) { size_t length = 0; + // Read all available data from the stream buffer. do { - size_t intended_data_size = 64; - uint8_t data[intended_data_size]; + // Read up to 64 bytes from the stream buffer. + size_t buffer_size = 64; + // Allocate a buffer for the data. + uint8_t data[buffer_size]; + // Read the data from the stream buffer. length = - furi_stream_buffer_receive(instance->rx_stream, data, intended_data_size, 0); - + furi_stream_buffer_receive(instance->camera_rx_stream, data, buffer_size, 0); if(length > 0) { with_view_model( instance->view, UartDumpModel * model, { + // Process the data. for(size_t i = 0; i < length; i++) { process_ringbuffer(model, data[i]); } @@ -488,7 +594,7 @@ CameraSuiteViewCamera* camera_suite_view_camera_alloc() { instance->view = view_alloc(); // Allocate a stream buffer - instance->rx_stream = furi_stream_buffer_alloc(2048, 1); + instance->camera_rx_stream = furi_stream_buffer_alloc(2048, 1); // Allocate model view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(UartDumpModel)); @@ -509,16 +615,17 @@ CameraSuiteViewCamera* camera_suite_view_camera_alloc() { view_set_exit_callback(instance->view, camera_suite_view_camera_exit); // Allocate a thread for this camera to run on. - FuriThread* thread = furi_thread_alloc_ex("UsbUartWorker", 2048, camera_worker, instance); - instance->worker_thread = thread; - furi_thread_start(instance->worker_thread); + FuriThread* thread = furi_thread_alloc_ex( + "Camera_Suite_Camera_Rx_Thread", 2048, camera_suite_camera_worker, instance); + instance->camera_worker_thread = thread; + furi_thread_start(instance->camera_worker_thread); - // 115200 is the default baud rate for the ESP32-CAM. + // Allocate the serial handle for the camera. instance->serial_handle = furi_hal_serial_control_acquire(UART_CH); furi_check(instance->serial_handle); furi_hal_serial_init(instance->serial_handle, 230400); - // Enable UART1 and set the IRQ callback. + // Start the asynchronous receive. furi_hal_serial_async_rx_start(instance->serial_handle, camera_on_irq_cb, instance, false); return instance; @@ -528,14 +635,14 @@ void camera_suite_view_camera_free(CameraSuiteViewCamera* instance) { furi_assert(instance); // Free the worker thread. - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), WorkerEventStop); - furi_thread_join(instance->worker_thread); - furi_thread_free(instance->worker_thread); + furi_thread_flags_set(furi_thread_get_id(instance->camera_worker_thread), WorkerEventStop); + furi_thread_join(instance->camera_worker_thread); + furi_thread_free(instance->camera_worker_thread); // Free the allocated stream buffer. - furi_stream_buffer_free(instance->rx_stream); + furi_stream_buffer_free(instance->camera_rx_stream); - // Re-enable the console. + // Deinitialize the serial handle and release the control. furi_hal_serial_deinit(instance->serial_handle); furi_hal_serial_control_release(instance->serial_handle); diff --git a/base_pack/camera_suite/views/camera_suite_view_camera.h b/base_pack/camera_suite/views/camera_suite_view_camera.h index ec781175c82..c33392832ea 100644 --- a/base_pack/camera_suite/views/camera_suite_view_camera.h +++ b/base_pack/camera_suite/views/camera_suite_view_camera.h @@ -17,8 +17,6 @@ #include "../helpers/camera_suite_custom_event.h" -#include - #define UART_CH (FuriHalSerialIdUsart) #define BITMAP_HEADER_LENGTH 62 @@ -43,16 +41,16 @@ typedef enum { WorkerEventRx = (1 << 2), } WorkerEventFlags; -#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) +#define CAMERA_WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) // Forward declaration typedef void (*CameraSuiteViewCameraCallback)(CameraSuiteCustomEvent event, void* context); typedef struct CameraSuiteViewCamera { CameraSuiteViewCameraCallback callback; - FuriStreamBuffer* rx_stream; + FuriStreamBuffer* camera_rx_stream; FuriHalSerialHandle* serial_handle; - FuriThread* worker_thread; + FuriThread* camera_worker_thread; NotificationApp* notification; View* view; void* context; diff --git a/base_pack/camera_suite/views/camera_suite_view_guide.c b/base_pack/camera_suite/views/camera_suite_view_guide.c index 27d2edd3ea2..4b848846f0e 100644 --- a/base_pack/camera_suite/views/camera_suite_view_guide.c +++ b/base_pack/camera_suite/views/camera_suite_view_guide.c @@ -30,7 +30,7 @@ void camera_suite_view_guide_draw(Canvas* canvas, CameraSuiteViewGuideModel* mod canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Guide"); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Camera Suite Guide"); canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned(canvas, 0, 12, AlignLeft, AlignTop, "Left = Toggle invert"); canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Right = Toggle dithering"); @@ -54,6 +54,7 @@ bool camera_suite_view_guide_input(InputEvent* event, void* context) { CameraSuiteViewGuideModel * model, { UNUSED(model); + // Go back to the main menu. instance->callback(CameraSuiteCustomEventSceneGuideBack, instance->context); }, true); diff --git a/base_pack/camera_suite/views/camera_suite_view_start.c b/base_pack/camera_suite/views/camera_suite_view_start.c index a84ee50c2c2..7376b0e1674 100644 --- a/base_pack/camera_suite/views/camera_suite_view_start.c +++ b/base_pack/camera_suite/views/camera_suite_view_start.c @@ -4,16 +4,6 @@ #include #include -struct CameraSuiteViewStart { - View* view; - CameraSuiteViewStartCallback callback; - void* context; -}; - -typedef struct { - int some_value; -} CameraSuiteViewStartModel; - void camera_suite_view_start_set_callback( CameraSuiteViewStart* instance, CameraSuiteViewStartCallback callback, @@ -27,13 +17,88 @@ void camera_suite_view_start_set_callback( void camera_suite_view_start_draw(Canvas* canvas, CameraSuiteViewStartModel* model) { UNUSED(model); canvas_clear(canvas); - canvas_set_color(canvas, ColorBlack); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Camera Suite"); + + // Draw Camera Suite logo. + canvas_draw_circle(canvas, 82, 28, 1); + canvas_draw_circle(canvas, 85, 29, 11); + canvas_draw_circle(canvas, 85, 29, 6); + canvas_draw_circle(canvas, 85, 29, 9); + canvas_draw_circle(canvas, 104, 17, 1); + canvas_draw_line(canvas, 70, 14, 108, 14); + canvas_draw_line(canvas, 70, 14, 74, 10); + canvas_draw_line(canvas, 70, 15, 70, 37); + canvas_draw_line(canvas, 70, 32, 74, 32); + canvas_draw_line(canvas, 75, 9, 82, 9); + canvas_draw_line(canvas, 78, 37, 70, 37); + canvas_draw_line(canvas, 79, 20, 70, 20); + canvas_draw_line(canvas, 81, 18, 85, 14); + canvas_draw_line(canvas, 82, 14, 82, 8); + canvas_draw_line(canvas, 82, 8, 86, 4); + canvas_draw_line(canvas, 82, 8, 95, 8); + canvas_draw_line(canvas, 83, 14, 85, 11); + canvas_draw_line(canvas, 84, 15, 92, 15); + canvas_draw_line(canvas, 86, 11, 92, 11); + canvas_draw_line(canvas, 86, 23, 84, 24); + canvas_draw_line(canvas, 86, 24, 82, 24); + canvas_draw_line(canvas, 86, 25, 89, 27); + canvas_draw_line(canvas, 86, 4, 98, 4); + canvas_draw_line(canvas, 87, 24, 91, 28); + canvas_draw_line(canvas, 87, 26, 89, 28); + canvas_draw_line(canvas, 88, 26, 84, 25); + canvas_draw_line(canvas, 88, 26, 86, 24); + canvas_draw_line(canvas, 88, 28, 89, 31); + canvas_draw_line(canvas, 89, 28, 89, 30); + canvas_draw_line(canvas, 90, 28, 90, 31); + canvas_draw_line(canvas, 90, 30, 89, 33); + canvas_draw_line(canvas, 92, 11, 93, 14); + canvas_draw_line(canvas, 93, 16, 97, 18); + canvas_draw_line(canvas, 94, 8, 98, 4); + canvas_draw_line(canvas, 95, 9, 95, 14); + canvas_draw_line(canvas, 96, 32, 107, 32); + canvas_draw_line(canvas, 98, 19, 100, 22); + canvas_draw_line(canvas, 98, 5, 98, 9); + canvas_draw_line(canvas, 98, 9, 111, 9); + canvas_draw_line(canvas, 98, 9, 96, 14); + canvas_draw_line(canvas, 99, 20, 108, 20); + canvas_draw_line(canvas, 100, 23, 100, 27); + canvas_draw_line(canvas, 100, 28, 93, 36); + canvas_draw_line(canvas, 102, 23, 104, 23); + canvas_draw_line(canvas, 104, 23, 106, 28); + canvas_draw_line(canvas, 104, 24, 102, 28); + canvas_draw_line(canvas, 107, 14, 112, 9); + canvas_draw_line(canvas, 107, 28, 108, 27); + canvas_draw_line(canvas, 107, 37, 92, 37); + canvas_draw_line(canvas, 108, 15, 108, 37); + canvas_draw_line(canvas, 108, 20, 112, 16); + canvas_draw_line(canvas, 108, 32, 112, 28); + canvas_draw_line(canvas, 108, 37, 112, 33); + canvas_draw_line(canvas, 112, 10, 112, 33); + + // Draw "Start" button. canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Flipper Zero"); - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "ESP32 CAM"); - elements_button_center(canvas, "Start"); + canvas_draw_str(canvas, 46, 57, "Start"); + canvas_draw_circle(canvas, 75, 53, 2); + canvas_draw_dot(canvas, 72, 50); + canvas_draw_dot(canvas, 72, 56); + canvas_draw_dot(canvas, 78, 50); + canvas_draw_dot(canvas, 78, 56); + canvas_draw_line(canvas, 43, 47, 43, 59); + canvas_draw_line(canvas, 44, 46, 81, 46); + canvas_draw_line(canvas, 44, 60, 81, 60); + canvas_draw_line(canvas, 71, 51, 71, 55); + canvas_draw_line(canvas, 73, 49, 77, 49); + canvas_draw_line(canvas, 73, 57, 77, 57); + canvas_draw_line(canvas, 74, 52, 76, 52); + canvas_draw_line(canvas, 74, 53, 76, 53); + canvas_draw_line(canvas, 74, 54, 77, 54); + canvas_draw_line(canvas, 79, 51, 79, 55); + canvas_draw_line(canvas, 82, 47, 82, 59); + + // Draw "Camera Suite" text. + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 16, 23, "Camera"); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 23, 35, "Suite"); } static void camera_suite_view_start_model_init(CameraSuiteViewStartModel* const model) { @@ -94,12 +159,22 @@ void camera_suite_view_start_enter(void* context) { } CameraSuiteViewStart* camera_suite_view_start_alloc() { + // Allocate memory for the instance CameraSuiteViewStart* instance = malloc(sizeof(CameraSuiteViewStart)); + + // Allocate the view object instance->view = view_alloc(); + + // Allocate model view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewStartModel)); - // furi_assert crashes in events without this + + // Set context for the view (furi_assert crashes in events without this) view_set_context(instance->view, instance); + + // Set draw callback view_set_draw_callback(instance->view, (ViewDrawCallback)camera_suite_view_start_draw); + + // Set input callback view_set_input_callback(instance->view, camera_suite_view_start_input); with_view_model( diff --git a/base_pack/camera_suite/views/camera_suite_view_start.h b/base_pack/camera_suite/views/camera_suite_view_start.h index e991cce92dd..f7116bb5b39 100644 --- a/base_pack/camera_suite/views/camera_suite_view_start.h +++ b/base_pack/camera_suite/views/camera_suite_view_start.h @@ -3,10 +3,18 @@ #include #include "../helpers/camera_suite_custom_event.h" -typedef struct CameraSuiteViewStart CameraSuiteViewStart; - typedef void (*CameraSuiteViewStartCallback)(CameraSuiteCustomEvent event, void* context); +typedef struct CameraSuiteViewStart { + View* view; + CameraSuiteViewStartCallback callback; + void* context; +} CameraSuiteViewStart; + +typedef struct { + int some_value; +} CameraSuiteViewStartModel; + void camera_suite_view_start_set_callback( CameraSuiteViewStart* camera_suite_view_start, CameraSuiteViewStartCallback callback, @@ -16,4 +24,4 @@ View* camera_suite_view_start_get_view(CameraSuiteViewStart* camera_suite_static CameraSuiteViewStart* camera_suite_view_start_alloc(); -void camera_suite_view_start_free(CameraSuiteViewStart* camera_suite_static); \ No newline at end of file +void camera_suite_view_start_free(CameraSuiteViewStart* camera_suite_static);