Browse Source

Merge camera_suite from https://github.com/CodyTolene/Flipper-Zero-Camera-Suite

# Conflicts:
#	camera_suite/application.fam
#	camera_suite/manifest.yml
#	camera_suite/views/camera_suite_view_camera.c
#	camera_suite/views/camera_suite_view_camera.h
Willy-JL 1 năm trước cách đây
mục cha
commit
7ce1e0a15c

+ 1 - 1
camera_suite/application.fam

@@ -7,7 +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_version="1.5",
+    fap_version="1.6",
     fap_weburl="https://github.com/CodyTolene/Flipper-Zero-Cam",
     name="[ESP32] Camera Suite",
     order=1,

+ 23 - 7
camera_suite/camera_suite.c

@@ -42,16 +42,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(
@@ -69,6 +71,12 @@ CameraSuite* camera_suite_app_alloc() {
         CameraSuiteViewIdCamera,
         camera_suite_view_camera_get_view(app->camera_suite_view_camera));
 
+    app->camera_suite_view_wifi_camera = camera_suite_view_wifi_camera_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        CameraSuiteViewIdWiFiCamera,
+        camera_suite_view_wifi_camera_get_view(app->camera_suite_view_wifi_camera));
+
     app->camera_suite_view_guide = camera_suite_view_guide_alloc();
     view_dispatcher_add_view(
         app->view_dispatcher,
@@ -80,7 +88,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
@@ -98,8 +110,10 @@ void camera_suite_app_free(CameraSuite* app) {
     view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdStartscreen);
     view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdMenu);
     view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdCamera);
+    view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdWiFiCamera);
     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);
@@ -108,7 +122,9 @@ void camera_suite_app_free(CameraSuite* app) {
     // Free remaining resources
     camera_suite_view_start_free(app->camera_suite_view_start);
     camera_suite_view_camera_free(app->camera_suite_view_camera);
+    camera_suite_view_wifi_camera_free(app->camera_suite_view_wifi_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);
 

+ 5 - 1
camera_suite/camera_suite.h

@@ -16,6 +16,7 @@
 #include "views/camera_suite_view_guide.h"
 #include "views/camera_suite_view_start.h"
 #include "views/camera_suite_view_camera.h"
+#include "views/camera_suite_view_wifi_camera.h"
 #include "helpers/camera_suite_storage.h"
 
 #include <assets_icons.h>
@@ -31,6 +32,7 @@ typedef struct {
     VariableItemList* variable_item_list;
     CameraSuiteViewStart* camera_suite_view_start;
     CameraSuiteViewCamera* camera_suite_view_camera;
+    CameraSuiteViewWiFiCamera* camera_suite_view_wifi_camera;
     CameraSuiteViewGuide* camera_suite_view_guide;
     uint32_t orientation;
     uint32_t dither;
@@ -46,8 +48,10 @@ typedef enum {
     CameraSuiteViewIdStartscreen,
     CameraSuiteViewIdMenu,
     CameraSuiteViewIdCamera,
+    CameraSuiteViewIdWiFiCamera,
     CameraSuiteViewIdGuide,
-    CameraSuiteViewIdSettings,
+    CameraSuiteViewIdAppSettings,
+    CameraSuiteViewIdCamSettings,
 } CameraSuiteViewId;
 
 typedef enum {

+ 12 - 4
camera_suite/docs/CHANGELOG.md

@@ -1,8 +1,19 @@
-## 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.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
 
@@ -16,9 +27,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

+ 5 - 5
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.

+ 7 - 0
camera_suite/helpers/camera_suite_custom_event.h

@@ -15,6 +15,13 @@ typedef enum {
     CameraSuiteCustomEventSceneCameraRight,
     CameraSuiteCustomEventSceneCameraOk,
     CameraSuiteCustomEventSceneCameraBack,
+    // Scene events: WiFi Camera
+    CameraSuiteCustomEventSceneWiFiCameraUp,
+    CameraSuiteCustomEventSceneWiFiCameraDown,
+    CameraSuiteCustomEventSceneWiFiCameraLeft,
+    CameraSuiteCustomEventSceneWiFiCameraRight,
+    CameraSuiteCustomEventSceneWiFiCameraOk,
+    CameraSuiteCustomEventSceneWiFiCameraBack,
     // Scene events: Guide
     CameraSuiteCustomEventSceneGuideUp,
     CameraSuiteCustomEventSceneGuideDown,

+ 2 - 0
camera_suite/helpers/camera_suite_storage.c

@@ -98,6 +98,8 @@ void camera_suite_read_settings(void* context) {
     }
     furi_string_free(temp_str);
 
+    furi_string_free(temp_str);
+
     if(file_version < BOILERPLATE_SETTINGS_FILE_VERSION) {
         FURI_LOG_I(TAG, "old config version, will be removed.");
         camera_suite_close_config_file(fff_file);

+ 22 - 0
camera_suite/manifest.yml

@@ -0,0 +1,22 @@
+author: "@CodyTolene @Z4urce @leedave"
+category: "GPIO"
+changelog: "@./docs/CHANGELOG.md"
+description: "@./docs/README.md"
+icon: "icons/camera_suite.png"
+id: "camera_suite"
+name: "[ESP32] Camera Suite"
+screenshots:
+  - "screenshots/camera.png"
+  - "screenshots/camera_preview.png"
+  - "screenshots/guide.png"
+  - "screenshots/guide_connect.png"
+  - "screenshots/main_menu.png"
+  - "screenshots/settings_app.png"
+  - "screenshots/settings_camera.png"
+short_description: "A camera suite application for the Flipper Zero ESP32-CAM module."
+sourcecode:
+  location:
+    commit_sha: ...
+    origin: https://github.com/CodyTolene/Flipper-Zero-Camera-Suite.git
+    subdir: fap
+  type: git

+ 111 - 0
camera_suite/scenes/camera_suite_scene_app_settings.c

@@ -0,0 +1,111 @@
+#include "../camera_suite.h"
+#include <lib/toolbox/value_index.h>
+
+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);
+}

+ 15 - 88
camera_suite/scenes/camera_suite_scene_settings.c → 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);
-}
+}

+ 3 - 1
camera_suite/scenes/camera_suite_scene_config.h

@@ -1,5 +1,7 @@
 ADD_SCENE(camera_suite, start, Start)
 ADD_SCENE(camera_suite, menu, Menu)
 ADD_SCENE(camera_suite, camera, Camera)
+ADD_SCENE(camera_suite, wifi_camera, WiFiCamera)
 ADD_SCENE(camera_suite, guide, Guide)
-ADD_SCENE(camera_suite, settings, Settings)
+ADD_SCENE(camera_suite, app_settings, AppSettings)
+ADD_SCENE(camera_suite, cam_settings, CamSettings)

+ 45 - 11
camera_suite/scenes/camera_suite_scene_menu.c

@@ -3,10 +3,14 @@
 enum SubmenuIndex {
     /** Camera. */
     SubmenuIndexSceneCamera = 10,
+    /** WiFi Camera */
+    SubmenuIndexSceneWiFiCamera,
+    /** 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 +23,36 @@ 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,
+        "Stream Camera to WiFi",
+        SubmenuIndexSceneWiFiCamera,
+        camera_suite_scene_menu_submenu_callback,
+        app);
+
     submenu_add_item(
-        app->submenu, "Guide", SubmenuIndexGuide, camera_suite_scene_menu_submenu_callback, app);
+        app->submenu,
+        "Camera Settings",
+        SubmenuIndexCamSettings,
+        camera_suite_scene_menu_submenu_callback,
+        app);
+
     submenu_add_item(
         app->submenu,
-        "Settings",
-        SubmenuIndexSettings,
+        "Application Settings",
+        SubmenuIndexAppSettings,
+        camera_suite_scene_menu_submenu_callback,
+        app);
+
+    submenu_add_item(
+        app->submenu,
+        "Camera Suite Guide",
+        SubmenuIndexGuide,
         camera_suite_scene_menu_submenu_callback,
         app);
 
@@ -52,16 +76,26 @@ 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 == SubmenuIndexSceneWiFiCamera) {
+            scene_manager_set_scene_state(
+                app->scene_manager, CameraSuiteSceneMenu, SubmenuIndexSceneWiFiCamera);
+            scene_manager_next_scene(app->scene_manager, CameraSuiteSceneWiFiCamera);
+            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;

+ 51 - 0
camera_suite/scenes/camera_suite_scene_wifi_camera.c

@@ -0,0 +1,51 @@
+#include "../camera_suite.h"
+#include "../helpers/camera_suite_custom_event.h"
+#include "../views/camera_suite_view_wifi_camera.h"
+
+void camera_suite_view_wifi_camera_callback(CameraSuiteCustomEvent event, void* context) {
+    furi_assert(context);
+    CameraSuite* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void camera_suite_scene_wifi_camera_on_enter(void* context) {
+    furi_assert(context);
+    CameraSuite* app = context;
+    camera_suite_view_wifi_camera_set_callback(
+        app->camera_suite_view_wifi_camera, camera_suite_view_wifi_camera_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, CameraSuiteViewIdWiFiCamera);
+}
+
+bool camera_suite_scene_wifi_camera_on_event(void* context, SceneManagerEvent event) {
+    CameraSuite* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case CameraSuiteCustomEventSceneWiFiCameraLeft:
+        case CameraSuiteCustomEventSceneWiFiCameraRight:
+        case CameraSuiteCustomEventSceneWiFiCameraUp:
+        case CameraSuiteCustomEventSceneWiFiCameraDown:
+            // Do nothing.
+            break;
+        case CameraSuiteCustomEventSceneWiFiCameraBack:
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, CameraSuiteSceneMenu)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void camera_suite_scene_wifi_camera_on_exit(void* context) {
+    CameraSuite* app = context;
+    UNUSED(app);
+}

BIN
camera_suite/screenshots/camera.png


BIN
camera_suite/screenshots/guide.png


BIN
camera_suite/screenshots/guide_connect.png


BIN
camera_suite/screenshots/main_menu.png


BIN
camera_suite/screenshots/settings.png


BIN
camera_suite/screenshots/settings_app.png


BIN
camera_suite/screenshots/settings_camera.png


BIN
camera_suite/screenshots/start_screen.png


+ 112 - 25
camera_suite/views/camera_suite_view_camera.c

@@ -59,22 +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) {
+        // 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, 10, 25, "VCC - 3V3");
+        canvas_draw_str(canvas, 91, 11, "VCC-3V");
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 73, 25, "GND - GND");
+        canvas_draw_str(canvas, 91, 27, "U0R-TX");
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 10, 11, "Connect the ESP32-CAM:");
+        canvas_draw_str(canvas, 91, 43, "U0T-RX");
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 10, 36, "U0R - TX");
+        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, 73, 36, "U0T - RX");
+        canvas_draw_str(canvas, 2, 40, "Please");
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 10, 49, "github.com/CodyTolene/");
+        canvas_draw_str(canvas, 2, 49, "Connect");
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str(canvas, 4, 60, "Flipper-Zero-Camera-Suite");
+        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);
     }
 }
 
@@ -391,11 +469,11 @@ static void
     CameraSuiteViewCamera* instance = context;
 
     // If `uartIrqEvent` is `UartIrqEventRXNE`, send the data to the
-    // `rx_stream` and set the `WorkerEventRx` flag.
+    // `camera_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);
     }
 }
 
@@ -447,31 +525,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]);
                             }
@@ -496,7 +582,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));
@@ -517,9 +603,10 @@ 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.
     instance->serial_handle = furi_hal_serial_control_acquire(UART_CH);
@@ -536,12 +623,12 @@ 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.
     furi_hal_serial_deinit(instance->serial_handle);

+ 3 - 3
camera_suite/views/camera_suite_view_camera.h

@@ -43,16 +43,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;

+ 2 - 1
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);

+ 92 - 17
camera_suite/views/camera_suite_view_start.c

@@ -4,16 +4,6 @@
 #include <input/input.h>
 #include <gui/elements.h>
 
-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(

+ 11 - 3
camera_suite/views/camera_suite_view_start.h

@@ -3,10 +3,18 @@
 #include <gui/view.h>
 #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);
+void camera_suite_view_start_free(CameraSuiteViewStart* camera_suite_static);

+ 170 - 0
camera_suite/views/camera_suite_view_wifi_camera.c

@@ -0,0 +1,170 @@
+#include "../camera_suite.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+#include "../helpers/camera_suite_haptic.h"
+#include "../helpers/camera_suite_speaker.h"
+#include "../helpers/camera_suite_led.h"
+
+void camera_suite_view_wifi_camera_set_callback(
+    CameraSuiteViewWiFiCamera* instance,
+    CameraSuiteViewWiFiCameraCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+static void camera_suite_view_wifi_camera_draw(Canvas* canvas, void* model) {
+    furi_assert(canvas);
+    furi_assert(model);
+
+    CameraSuiteViewWiFiCameraModel* instance = model;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_frame(canvas, 0, 0, FRAME_WIDTH, FRAME_HEIGHT);
+
+    canvas_draw_str_aligned(canvas, 3, 3, AlignLeft, AlignTop, "Feature coming soon!");
+
+    // Draw log from camera.
+    canvas_draw_str_aligned(
+        canvas, 3, 13, AlignLeft, AlignTop, furi_string_get_cstr(instance->log));
+}
+
+static bool camera_suite_view_wifi_camera_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    furi_assert(event);
+
+    CameraSuiteViewWiFiCamera* instance = context;
+
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        default:
+            with_view_model(
+                instance->view,
+                CameraSuiteViewWiFiCameraModel * model,
+                {
+                    UNUSED(model);
+                    // Stop all sounds, reset the LED.
+                    camera_suite_play_bad_bump(instance->context);
+                    camera_suite_stop_all_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 0);
+                },
+                true);
+            break;
+        }
+    } else if(event->type == InputTypePress) {
+        switch(event->key) {
+        case InputKeyBack: {
+            with_view_model(
+                instance->view,
+                CameraSuiteViewWiFiCameraModel * model,
+                {
+                    UNUSED(model);
+
+                    // Stop camera WiFi stream.
+                    // furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t[]){'w'}, 1);
+                    // furi_delay_ms(50);
+
+                    // Go back to the main menu.
+                    instance->callback(CameraSuiteCustomEventSceneCameraBack, instance->context);
+                },
+                true);
+            break;
+        }
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+        case InputKeyMAX:
+        default: {
+            break;
+        }
+        }
+    }
+
+    return false;
+}
+
+static void camera_suite_view_wifi_camera_exit(void* context) {
+    furi_assert(context);
+}
+
+static void camera_suite_view_wifi_camera_model_init(CameraSuiteViewWiFiCameraModel* const model) {
+    model->log = furi_string_alloc();
+    furi_string_reserve(model->log, 4096);
+}
+
+static void camera_suite_view_wifi_camera_enter(void* context) {
+    furi_assert(context);
+
+    // Get the camera suite instance context.
+    CameraSuiteViewWiFiCamera* instance = (CameraSuiteViewWiFiCamera*)context;
+
+    // Start wifi camera stream.
+    // furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t[]){'W'}, 1);
+
+    with_view_model(
+        instance->view,
+        CameraSuiteViewWiFiCameraModel * model,
+        { camera_suite_view_wifi_camera_model_init(model); },
+        true);
+}
+
+CameraSuiteViewWiFiCamera* camera_suite_view_wifi_camera_alloc() {
+    // Allocate memory for the instance
+    CameraSuiteViewWiFiCamera* instance = malloc(sizeof(CameraSuiteViewWiFiCamera));
+
+    // Allocate the view object
+    instance->view = view_alloc();
+
+    // Allocate model
+    view_allocate_model(
+        instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewWiFiCameraModel));
+
+    // 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_wifi_camera_draw);
+
+    // Set input callback
+    view_set_input_callback(instance->view, camera_suite_view_wifi_camera_input);
+
+    // Set enter callback
+    view_set_enter_callback(instance->view, camera_suite_view_wifi_camera_enter);
+
+    // Set exit callback
+    view_set_exit_callback(instance->view, camera_suite_view_wifi_camera_exit);
+
+    with_view_model(
+        instance->view,
+        CameraSuiteViewWiFiCameraModel * model,
+        { camera_suite_view_wifi_camera_model_init(model); },
+        true);
+
+    return instance;
+}
+
+void camera_suite_view_wifi_camera_free(CameraSuiteViewWiFiCamera* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        CameraSuiteViewWiFiCameraModel * model,
+        { furi_string_free(model->log); },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* camera_suite_view_wifi_camera_get_view(CameraSuiteViewWiFiCamera* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 42 - 0
camera_suite/views/camera_suite_view_wifi_camera.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include <gui/modules/text_box.h>
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_uart.h>
+#include <gui/elements.h>
+#include <gui/gui.h>
+#include <gui/icon_i.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <storage/filesystem_api_defines.h>
+#include <storage/storage.h>
+
+#include "../helpers/camera_suite_custom_event.h"
+
+typedef void (*CameraSuiteViewWiFiCameraCallback)(CameraSuiteCustomEvent event, void* context);
+
+typedef struct CameraSuiteViewWiFiCamera {
+    View* view;
+    CameraSuiteViewCameraCallback callback;
+    void* context;
+} CameraSuiteViewWiFiCamera;
+
+typedef struct {
+    FuriString* log;
+} CameraSuiteViewWiFiCameraModel;
+
+CameraSuiteViewWiFiCamera* camera_suite_view_wifi_camera_alloc();
+
+View* camera_suite_view_wifi_camera_get_view(CameraSuiteViewWiFiCamera* camera_suite_static);
+
+void camera_suite_view_wifi_camera_free(CameraSuiteViewWiFiCamera* camera_suite_static);
+
+void camera_suite_view_wifi_camera_set_callback(
+    CameraSuiteViewWiFiCamera* camera_suite_view_wifi_camera,
+    CameraSuiteViewWiFiCameraCallback callback,
+    void* context);