diff --git a/.github/workflows/pull-request-dev.yml b/.github/workflows/pull-request-dev.yml index 35f5536..818104f 100644 --- a/.github/workflows/pull-request-dev.yml +++ b/.github/workflows/pull-request-dev.yml @@ -6,7 +6,19 @@ on: jobs: ufbt-test-build-action: runs-on: ubuntu-latest - name: "PR Build: dev" + strategy: + matrix: + include: + - name: "dev" + sdk-channel: dev + + - name: "rc" + sdk-channel: rc + + - name: "release" + sdk-channel: release + + name: "PR Build: ${{ matrix.name }}" steps: - name: "Checkout" uses: actions/checkout@v3 @@ -20,7 +32,7 @@ jobs: id: build-app with: app-dir: ./fap - sdk-channel: dev + sdk-channel: ${{ matrix.sdk-channel }} - name: "Lint" uses: flipperdevices/flipperzero-ufbt-action@v0.1 @@ -28,3 +40,9 @@ jobs: app-dir: ./fap skip-setup: true task: lint + + - name: "Create FAP Download Pre Release (${{ matrix.name }})" + uses: actions/upload-artifact@v3 + with: + name: ${{ github.event.repository.name }}-${{ matrix.name }}-${{ steps.build-app.outputs.suffix }}.zip + path: ${{ steps.build-app.outputs.fap-artifacts }} diff --git a/fap/camera_suite.c b/fap/camera_suite.c index c8289d7..f176343 100644 --- a/fap/camera_suite.c +++ b/fap/camera_suite.c @@ -1,6 +1,4 @@ #include "camera_suite.h" -#include -#include bool camera_suite_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -72,6 +70,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, @@ -105,6 +109,7 @@ 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, CameraSuiteViewIdAppSettings); view_dispatcher_remove_view(app->view_dispatcher, CameraSuiteViewIdCamSettings); @@ -116,6 +121,7 @@ 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); diff --git a/fap/camera_suite.h b/fap/camera_suite.h index 47cccf9..4547533 100644 --- a/fap/camera_suite.h +++ b/fap/camera_suite.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -16,10 +17,41 @@ #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" #define TAG "Camera Suite" +#ifdef FW_ORIGIN_Xtreme +/** + * Enable the following line for "Xtreme Firmware" & "Xtreme Apps" (Flipper-XFW). + * + * @see https://github.com/Flipper-XFW/Xtreme-Firmware + * @see https://github.com/Flipper-XFW/Xtreme-Apps +*/ +#include +#define UART_CH (xtreme_settings.uart_esp_channel) +#elif defined FW_ORIGIN_Momentum +/** + * Enable the following line for "Momentum Firmware" & "Momentum Apps". + * + * @see https://github.com/Next-Flip/Momentum-Firmware + * @see https://github.com/Next-Flip/Momentum-Apps +*/ +#include +#define UART_CH (momentum_settings.uart_esp_channel) +#elif defined FW_ORIGIN_RM +/** + * Enable the following line for "RogueMaster Firmware". + * + * @see https://github.com/RogueMaster/flipperzero-firmware-wPlugins +*/ +#include +#define UART_CH (cfw_settings.uart_esp_channel) +#else +#define UART_CH (FuriHalSerialIdUsart) +#endif + typedef struct { Gui* gui; NotificationApp* notification; @@ -29,6 +61,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; @@ -44,11 +77,23 @@ typedef enum { CameraSuiteViewIdStartscreen, CameraSuiteViewIdMenu, CameraSuiteViewIdCamera, + CameraSuiteViewIdWiFiCamera, CameraSuiteViewIdGuide, CameraSuiteViewIdAppSettings, CameraSuiteViewIdCamSettings, } CameraSuiteViewId; +typedef enum { + // Reserved for StreamBuffer internal event + WorkerEventReserved = (1 << 0), + // Stop worker thread + WorkerEventStop = (1 << 1), + // Rx event + WorkerEventRx = (1 << 2), +} WorkerEventFlags; + +#define CAMERA_WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) + typedef enum { CameraSuiteOrientation0, CameraSuiteOrientation90, diff --git a/fap/helpers/camera_suite_custom_event.h b/fap/helpers/camera_suite_custom_event.h index 4d472d5..eb545b7 100644 --- a/fap/helpers/camera_suite_custom_event.h +++ b/fap/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, diff --git a/fap/helpers/camera_suite_storage.c b/fap/helpers/camera_suite_storage.c index 712346d..243280d 100644 --- a/fap/helpers/camera_suite_storage.c +++ b/fap/helpers/camera_suite_storage.c @@ -79,6 +79,7 @@ void camera_suite_read_settings(void* context) { camera_suite_close_storage(); return; } + uint32_t file_version; FuriString* temp_str = furi_string_alloc(); @@ -96,6 +97,7 @@ void camera_suite_read_settings(void* context) { furi_string_free(temp_str); return; } + furi_string_free(temp_str); if(file_version < BOILERPLATE_SETTINGS_FILE_VERSION) { diff --git a/fap/scenes/camera_suite_scene_config.h b/fap/scenes/camera_suite_scene_config.h index a9f0e05..fce4c9e 100644 --- a/fap/scenes/camera_suite_scene_config.h +++ b/fap/scenes/camera_suite_scene_config.h @@ -1,6 +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, app_settings, AppSettings) ADD_SCENE(camera_suite, cam_settings, CamSettings) diff --git a/fap/scenes/camera_suite_scene_menu.c b/fap/scenes/camera_suite_scene_menu.c index c6c8803..a1ca022 100644 --- a/fap/scenes/camera_suite_scene_menu.c +++ b/fap/scenes/camera_suite_scene_menu.c @@ -3,6 +3,8 @@ enum SubmenuIndex { /** Camera. */ SubmenuIndexSceneCamera = 10, + /** WiFi Camera */ + SubmenuIndexSceneWiFiCamera, /** Cam settings menu. */ SubmenuIndexCamSettings, /** App settings menu. */ @@ -26,6 +28,13 @@ void camera_suite_scene_menu_on_enter(void* context) { 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, "Camera Settings", @@ -67,6 +76,11 @@ 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); diff --git a/fap/scenes/camera_suite_scene_wifi_camera.c b/fap/scenes/camera_suite_scene_wifi_camera.c new file mode 100644 index 0000000..2df3db8 --- /dev/null +++ b/fap/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); +} \ No newline at end of file diff --git a/fap/views/camera_suite_view_camera.c b/fap/views/camera_suite_view_camera.c index dd3ba5f..17e645e 100644 --- a/fap/views/camera_suite_view_camera.c +++ b/fap/views/camera_suite_view_camera.c @@ -1,14 +1,16 @@ + #include "../camera_suite.h" -#include -#include -#include -#include -#include +#include "camera_suite_view_camera.h" + #include "../helpers/camera_suite_haptic.h" -#include "../helpers/camera_suite_speaker.h" #include "../helpers/camera_suite_led.h" +#include "../helpers/camera_suite_speaker.h" -static void draw_pixel_by_orientation(Canvas* canvas, uint8_t x, uint8_t y, uint8_t orientation) { +static void camera_suite_view_camera_draw_pixel_by_orientation( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t orientation) { furi_assert(canvas); furi_assert(x); furi_assert(y); @@ -21,7 +23,6 @@ static void draw_pixel_by_orientation(Canvas* canvas, uint8_t x, uint8_t y, uint break; } case 1: { // Camera rotated 90 degrees - canvas_draw_dot(canvas, y, FRAME_WIDTH - 1 - x); break; } @@ -40,7 +41,7 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) { furi_assert(canvas); furi_assert(model); - UartDumpModel* uartDumpModel = model; + CameraSuiteViewCameraModel* cameraSuiteViewCameraModel = model; // Clear the screen. canvas_set_color(canvas, ColorBlack); @@ -53,14 +54,15 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) { uint8_t y = p / ROW_BUFFER_LENGTH; // 0 .. 63 for(uint8_t i = 0; i < 8; ++i) { - if((uartDumpModel->pixels[p] & (1 << (7 - i))) != 0) { - draw_pixel_by_orientation(canvas, (x * 8) + i, y, uartDumpModel->orientation); + if((cameraSuiteViewCameraModel->pixels[p] & (1 << (7 - i))) != 0) { + camera_suite_view_camera_draw_pixel_by_orientation( + canvas, (x * 8) + i, y, cameraSuiteViewCameraModel->orientation); } } } // Draw the pinout guide if the camera is not initialized. - if(!uartDumpModel->is_initialized) { + if(!cameraSuiteViewCameraModel->is_initialized) { // Clear the screen. canvas_clear(canvas); @@ -156,10 +158,66 @@ static void camera_suite_view_camera_draw(Canvas* canvas, void* model) { } } -static void save_image_to_flipper_sd_card(void* model) { +static void camera_suite_view_camera_model_init( + CameraSuiteViewCameraModel* const model, + CameraSuite* instance_context) { furi_assert(model); + furi_assert(instance_context); + + model->is_initialized = false; + model->is_dithering_enabled = true; + model->is_inverted = false; + uint32_t orientation = instance_context->orientation; + model->orientation = orientation; + + for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) { + model->pixels[i] = 0; + } +} + +static void camera_suite_view_camera_enter(void* context) { + furi_assert(context); - UartDumpModel* uartDumpModel = model; + // Get the camera suite instance context. + CameraSuiteViewCamera* instance = (CameraSuiteViewCamera*)context; + + // Get the camera suite instance context. + CameraSuite* instance_context = instance->context; + + // Start camera stream. + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'S'}, 1); + furi_delay_ms(50); + + // Get/set dither type. + uint8_t dither_type = instance_context->dither; + furi_hal_serial_tx(instance->camera_serial_handle, &dither_type, 1); + furi_delay_ms(50); + + // Make sure the camera is not inverted. + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'i'}, 1); + furi_delay_ms(50); + + // Toggle flash on or off based on the current state. If the user has this + // on the flash will stay on the entire time the user is in the camera view. + uint8_t flash_state = instance_context->flash ? 'F' : 'f'; + furi_hal_serial_tx(instance->camera_serial_handle, &flash_state, 1); + furi_delay_ms(50); + + with_view_model( + instance->view, + CameraSuiteViewCameraModel * model, + { camera_suite_view_camera_model_init(model, instance_context); }, + true); +} + +static void camera_suite_view_camera_exit(void* context) { + furi_assert(context); +} + +static void camera_suite_view_camera_save_image_to_flipper_sd_card(void* model) { + furi_assert(model); + + CameraSuiteViewCameraModel* cameraSuiteViewCameraModel = model; // This pointer is used to access the storage. Storage* storage = furi_record_open(RECORD_STORAGE); @@ -202,9 +260,9 @@ static void save_image_to_flipper_sd_card(void* model) { // Free the file name after use. furi_string_free(file_name); - if(!uartDumpModel->is_inverted) { + if(!cameraSuiteViewCameraModel->is_inverted) { for(size_t i = 0; i < FRAME_BUFFER_LENGTH; ++i) { - uartDumpModel->pixels[i] = ~uartDumpModel->pixels[i]; + cameraSuiteViewCameraModel->pixels[i] = ~cameraSuiteViewCameraModel->pixels[i]; } } @@ -223,7 +281,8 @@ static void save_image_to_flipper_sd_card(void* model) { // @todo - Save image based on orientation. for(size_t i = 64; i > 0; --i) { for(size_t j = 0; j < ROW_BUFFER_LENGTH; ++j) { - row_buffer[j] = uartDumpModel->pixels[((i - 1) * ROW_BUFFER_LENGTH) + j]; + row_buffer[j] = + cameraSuiteViewCameraModel->pixels[((i - 1) * ROW_BUFFER_LENGTH) + j]; } storage_file_write(file, row_buffer, ROW_BUFFER_LENGTH); } @@ -236,21 +295,6 @@ static void save_image_to_flipper_sd_card(void* model) { storage_file_free(file); } -static void - camera_suite_view_camera_model_init(UartDumpModel* const model, CameraSuite* instance_context) { - furi_assert(model); - furi_assert(instance_context); - - model->is_dithering_enabled = true; - model->is_inverted = false; - uint32_t orientation = instance_context->orientation; - model->orientation = orientation; - - for(size_t i = 0; i < FRAME_BUFFER_LENGTH; i++) { - model->pixels[i] = 0; - } -} - static bool camera_suite_view_camera_input(InputEvent* event, void* context) { furi_assert(context); furi_assert(event); @@ -262,9 +306,10 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { default: // Stop all sounds, reset the LED. with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * 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); @@ -277,12 +322,12 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyBack: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { UNUSED(model); // Stop camera stream. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'s'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'s'}, 1); furi_delay_ms(50); // Go back to the main menu. @@ -294,7 +339,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyLeft: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { // Play sound. camera_suite_play_happy_bump(instance->context); @@ -303,13 +348,13 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { if(model->is_inverted) { // Camera: Set invert to false on the ESP32-CAM. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'i'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'i'}, 1); furi_delay_ms(50); model->is_inverted = false; } else { // Camera: Set invert to true on the ESP32-CAM. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'I'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'I'}, 1); furi_delay_ms(50); model->is_inverted = true; @@ -323,7 +368,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyRight: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { // Play sound. camera_suite_play_happy_bump(instance->context); @@ -332,13 +377,13 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { if(model->is_dithering_enabled) { // Camera: Disable dithering. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'d'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'d'}, 1); furi_delay_ms(50); model->is_dithering_enabled = false; } else { // Camera: Enable dithering. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'D'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'D'}, 1); furi_delay_ms(50); model->is_dithering_enabled = true; @@ -352,7 +397,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyUp: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { UNUSED(model); @@ -362,7 +407,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { camera_suite_led_set_rgb(instance->context, 0, 0, 255); // Camera: Increase contrast. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'C'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'C'}, 1); furi_delay_ms(50); instance->callback(CameraSuiteCustomEventSceneCameraUp, instance->context); @@ -373,7 +418,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyDown: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { UNUSED(model); @@ -383,7 +428,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { camera_suite_led_set_rgb(instance->context, 0, 0, 255); // Camera: Reduce contrast. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'c'}, 1); + furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'c'}, 1); furi_delay_ms(50); instance->callback(CameraSuiteCustomEventSceneCameraDown, instance->context); @@ -394,7 +439,7 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { case InputKeyOk: { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { // Play sound. camera_suite_play_long_bump(instance->context); @@ -402,10 +447,10 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { camera_suite_led_set_rgb(instance->context, 0, 0, 255); // @todo - Save picture directly to ESP32-CAM. - // furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'P'}, 1); + // furi_hal_serial_tx(instance->camera_serial_handle, (uint8_t[]){'P'}, 1); // Save currently displayed image to the Flipper Zero SD card. - save_image_to_flipper_sd_card(model); + camera_suite_view_camera_save_image_to_flipper_sd_card(model); instance->callback(CameraSuiteCustomEventSceneCameraOk, instance->context); }, @@ -422,47 +467,10 @@ static bool camera_suite_view_camera_input(InputEvent* event, void* context) { return false; } -static void camera_suite_view_camera_exit(void* context) { - furi_assert(context); -} - -static void camera_suite_view_camera_enter(void* context) { - furi_assert(context); - - // Get the camera suite instance context. - CameraSuiteViewCamera* instance = (CameraSuiteViewCamera*)context; - - // Get the camera suite instance context. - CameraSuite* instance_context = instance->context; - - // Start camera stream. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'S'}, 1); - furi_delay_ms(50); - - // Get/set dither type. - uint8_t dither_type = instance_context->dither; - furi_hal_serial_tx(instance->serial_handle, &dither_type, 1); - furi_delay_ms(50); - - // Make sure the camera is not inverted. - furi_hal_serial_tx(instance->serial_handle, (uint8_t[]){'i'}, 1); - furi_delay_ms(50); - - // Toggle flash on or off based on the current state. If the user has this - // on the flash will stay on the entire time the user is in the camera view. - uint8_t flash_state = instance_context->flash ? 'F' : 'f'; - furi_hal_serial_tx(instance->serial_handle, &flash_state, 1); - furi_delay_ms(50); - - with_view_model( - instance->view, - UartDumpModel * model, - { camera_suite_view_camera_model_init(model, instance_context); }, - true); -} - -static void - camera_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { +static void camera_suite_view_camera_on_irq_cb( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { furi_assert(handle); furi_assert(context); @@ -476,7 +484,9 @@ static void } } -static void process_ringbuffer(UartDumpModel* model, uint8_t const byte) { +static void camera_suite_view_camera_process_ringbuffer( + CameraSuiteViewCameraModel* model, + uint8_t const byte) { furi_assert(model); furi_assert(byte); @@ -524,7 +534,7 @@ static void process_ringbuffer(UartDumpModel* model, uint8_t const byte) { } } -static int32_t camera_suite_camera_worker(void* context) { +static int32_t camera_suite_view_camera_worker(void* context) { furi_assert(context); CameraSuiteViewCamera* instance = context; @@ -554,11 +564,11 @@ static int32_t camera_suite_camera_worker(void* context) { if(length > 0) { with_view_model( instance->view, - UartDumpModel * model, + CameraSuiteViewCameraModel * model, { // Process the data. for(size_t i = 0; i < length; i++) { - process_ringbuffer(model, data[i]); + camera_suite_view_camera_process_ringbuffer(model, data[i]); } }, false); @@ -566,13 +576,15 @@ static int32_t camera_suite_camera_worker(void* context) { } while(length > 0); with_view_model( - instance->view, UartDumpModel * model, { UNUSED(model); }, true); + instance->view, CameraSuiteViewCameraModel * model, { UNUSED(model); }, true); } } return 0; } +// ------------------------------------------------------------ // + CameraSuiteViewCamera* camera_suite_view_camera_alloc() { // Allocate memory for the instance CameraSuiteViewCamera* instance = malloc(sizeof(CameraSuiteViewCamera)); @@ -584,9 +596,9 @@ CameraSuiteViewCamera* camera_suite_view_camera_alloc() { instance->camera_rx_stream = furi_stream_buffer_alloc(2048, 1); // Allocate model - view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(UartDumpModel)); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(CameraSuiteViewCameraModel)); - // Set context for the view + // Set context for the view (furi_assert crashes in events without this) view_set_context(instance->view, instance); // Set draw callback @@ -603,21 +615,37 @@ CameraSuiteViewCamera* camera_suite_view_camera_alloc() { // Allocate a thread for this camera to run on. FuriThread* thread = furi_thread_alloc_ex( - "Camera_Suite_Camera_Rx_Thread", 2048, camera_suite_camera_worker, instance); + "Camera_Suite_Camera_Rx_Thread", 2048, camera_suite_view_camera_worker, instance); instance->camera_worker_thread = thread; furi_thread_start(instance->camera_worker_thread); // 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); + instance->camera_serial_handle = furi_hal_serial_control_acquire(UART_CH); + furi_assert(instance->camera_serial_handle); + furi_hal_serial_init(instance->camera_serial_handle, 230400); // Start the asynchronous receive. - furi_hal_serial_async_rx_start(instance->serial_handle, camera_on_irq_cb, instance, false); + furi_hal_serial_async_rx_start( + instance->camera_serial_handle, camera_suite_view_camera_on_irq_cb, instance, false); + + // Power cycle the camera off to start the firmware fresh. + furi_hal_power_disable_external_3_3v(); + furi_hal_power_disable_otg(); + + furi_delay_ms(100); + + // Power cycle the camera on. + furi_hal_power_enable_external_3_3v(); + furi_hal_power_enable_otg(); return instance; } +View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* instance) { + furi_assert(instance); + return instance->view; +} + void camera_suite_view_camera_free(CameraSuiteViewCamera* instance) { furi_assert(instance); @@ -629,21 +657,22 @@ void camera_suite_view_camera_free(CameraSuiteViewCamera* instance) { // Free the allocated stream buffer. furi_stream_buffer_free(instance->camera_rx_stream); + furi_assert(instance->camera_serial_handle); + // Deinitialize the serial handle and release the control. - furi_hal_serial_deinit(instance->serial_handle); - furi_hal_serial_control_release(instance->serial_handle); + furi_hal_serial_deinit(instance->camera_serial_handle); + furi_hal_serial_control_release(instance->camera_serial_handle); + + instance->camera_serial_handle = NULL; + instance->camera_worker_thread = NULL; + instance->camera_rx_stream = NULL; with_view_model( - instance->view, UartDumpModel * model, { UNUSED(model); }, true); + instance->view, CameraSuiteViewCameraModel * model, { UNUSED(model); }, true); view_free(instance->view); free(instance); } -View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* instance) { - furi_assert(instance); - return instance->view; -} - void camera_suite_view_camera_set_callback( CameraSuiteViewCamera* instance, CameraSuiteViewCameraCallback callback, @@ -652,4 +681,4 @@ void camera_suite_view_camera_set_callback( furi_assert(callback); instance->callback = callback; instance->context = context; -} \ No newline at end of file +} diff --git a/fap/views/camera_suite_view_camera.h b/fap/views/camera_suite_view_camera.h index babdd0e..9434de7 100644 --- a/fap/views/camera_suite_view_camera.h +++ b/fap/views/camera_suite_view_camera.h @@ -1,52 +1,5 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../helpers/camera_suite_custom_event.h" - -#ifdef FW_ORIGIN_Xtreme -/** - * Enable the following line for "Xtreme Firmware" & "Xtreme Apps" (Flipper-XFW). - * - * @see https://github.com/Flipper-XFW/Xtreme-Firmware - * @see https://github.com/Flipper-XFW/Xtreme-Apps -*/ -#include -#define UART_CH (xtreme_settings.uart_esp_channel) -#elif defined FW_ORIGIN_Momentum -/** - * Enable the following line for "Momentum Firmware" & "Momentum Apps". - * - * @see https://github.com/Next-Flip/Momentum-Firmware - * @see https://github.com/Next-Flip/Momentum-Apps -*/ -#include -#define UART_CH (momentum_settings.uart_esp_channel) -#elif defined FW_ORIGIN_RM -/** - * Enable the following line for "RogueMaster Firmware". - * - * @see https://github.com/RogueMaster/flipperzero-firmware-wPlugins -*/ -#include -#define UART_CH (cfw_settings.uart_esp_channel) -#else -#define UART_CH (FuriHalSerialIdUsart) -#endif - #define BITMAP_HEADER_LENGTH 62 #define FRAME_BIT_DEPTH 1 #define FRAME_BUFFER_LENGTH 1024 @@ -63,44 +16,33 @@ static const unsigned char bitmap_header[BITMAP_HEADER_LENGTH] = { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00}; -typedef enum { - WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event - WorkerEventStop = (1 << 1), - WorkerEventRx = (1 << 2), -} WorkerEventFlags; - -#define CAMERA_WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) - -// Forward declaration typedef void (*CameraSuiteViewCameraCallback)(CameraSuiteCustomEvent event, void* context); typedef struct CameraSuiteViewCamera { CameraSuiteViewCameraCallback callback; FuriStreamBuffer* camera_rx_stream; - FuriHalSerialHandle* serial_handle; + FuriHalSerialHandle* camera_serial_handle; FuriThread* camera_worker_thread; NotificationApp* notification; View* view; void* context; } CameraSuiteViewCamera; -typedef struct UartDumpModel { +typedef struct CameraSuiteViewCameraModel { bool is_dithering_enabled; bool is_initialized; bool is_inverted; - int rotation_angle; uint32_t orientation; uint8_t pixels[FRAME_BUFFER_LENGTH]; uint8_t ringbuffer_index; uint8_t row_identifier; uint8_t row_ringbuffer[RING_BUFFER_LENGTH]; -} UartDumpModel; +} CameraSuiteViewCameraModel; -// Function Prototypes CameraSuiteViewCamera* camera_suite_view_camera_alloc(); -View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* camera_suite_static); -void camera_suite_view_camera_free(CameraSuiteViewCamera* camera_suite_static); +View* camera_suite_view_camera_get_view(CameraSuiteViewCamera* instance); +void camera_suite_view_camera_free(CameraSuiteViewCamera* instance); void camera_suite_view_camera_set_callback( - CameraSuiteViewCamera* camera_suite_view_camera, + CameraSuiteViewCamera* instance, CameraSuiteViewCameraCallback callback, void* context); diff --git a/fap/views/camera_suite_view_wifi_camera.c b/fap/views/camera_suite_view_wifi_camera.c new file mode 100644 index 0000000..ecc7eee --- /dev/null +++ b/fap/views/camera_suite_view_wifi_camera.c @@ -0,0 +1,170 @@ +#include "../camera_suite.h" +#include "camera_suite_view_wifi_camera.h" + +#include "../helpers/camera_suite_haptic.h" +#include "../helpers/camera_suite_speaker.h" +#include "../helpers/camera_suite_led.h" + +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 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; + + // @TODO Start wifi camera stream. + // furi_hal_serial_tx(instance->wifi_serial_handle, (uint8_t[]){'W'}, 1); + // furi_delay_ms(50); + + with_view_model( + instance->view, + CameraSuiteViewWiFiCameraModel * model, + { camera_suite_view_wifi_camera_model_init(model); }, + true); +} + +static void camera_suite_view_wifi_camera_exit(void* context) { + furi_assert(context); +} + +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); + + // @TODO Stop camera WiFi stream. + // furi_hal_serial_tx(instance->wifi_serial_handle, (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; +} + +// ------------------------------------------------------------ // + +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; +} + +View* camera_suite_view_wifi_camera_get_view(CameraSuiteViewWiFiCamera* instance) { + furi_assert(instance); + return instance->view; +} + +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); +} + +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; +} diff --git a/fap/views/camera_suite_view_wifi_camera.h b/fap/views/camera_suite_view_wifi_camera.h new file mode 100644 index 0000000..38acf90 --- /dev/null +++ b/fap/views/camera_suite_view_wifi_camera.h @@ -0,0 +1,25 @@ +#pragma once + +typedef void (*CameraSuiteViewWiFiCameraCallback)(CameraSuiteCustomEvent event, void* context); + +typedef struct CameraSuiteViewWiFiCamera { + CameraSuiteViewWiFiCameraCallback callback; + FuriStreamBuffer* wifi_rx_stream; + FuriHalSerialHandle* wifi_serial_handle; + FuriThread* wifi_worker_thread; + NotificationApp* notification; + View* view; + void* context; +} CameraSuiteViewWiFiCamera; + +typedef struct CameraSuiteViewWiFiCameraModel { + FuriString* log; +} CameraSuiteViewWiFiCameraModel; + +CameraSuiteViewWiFiCamera* camera_suite_view_wifi_camera_alloc(); +View* camera_suite_view_wifi_camera_get_view(CameraSuiteViewWiFiCamera* instance); +void camera_suite_view_wifi_camera_free(CameraSuiteViewWiFiCamera* instance); +void camera_suite_view_wifi_camera_set_callback( + CameraSuiteViewWiFiCamera* instance, + CameraSuiteViewWiFiCameraCallback callback, + void* context); diff --git a/firmware-assets.bat b/firmware-assets.bat index 0a52a4b..33b684f 100644 --- a/firmware-assets.bat +++ b/firmware-assets.bat @@ -59,6 +59,9 @@ echo Checking and setting arduino-cli config... arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.data %CLI_TEMP%\data arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.downloads %CLI_TEMP%\downloads arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.user %CLI_TEMP%\user %* +rem Enable for Git installations (ie `arduino-cli lib install --git-url`). +rem @See "https://arduino.github.io/arduino-cli/0.35/configuration/#configuration-keys" +arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set library.enable_unsafe_install true echo Fetching assets... @@ -76,6 +79,9 @@ if %DATA_FLAG% gtr 0 ( :installAssets arduino-cli %ARDUINO_CLI_CONFIG_FILE% core update-index arduino-cli %ARDUINO_CLI_CONFIG_FILE% core install esp32:esp32 + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/ESPAsyncWebServer.git + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/AsyncTCP.git + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/ESPAsyncTCP.git goto :wrapUp ) else ( set /p SHOULD_REINSTALL="Assets already installed. Reinstall? (Y/N): " @@ -97,6 +103,7 @@ echo Resetting arduino-cli config back to defaults... arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.data C:\temp\arduino-cli\data arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.downloads C:\temp\arduino-cli\staging arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.user C:\temp\arduino-cli\user +arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set library.enable_unsafe_install false echo. echo The ESP32-CAM development dependencies were installed successfully. diff --git a/firmware-flash.bat b/firmware-flash.bat index 5f95698..195ebd9 100644 --- a/firmware-flash.bat +++ b/firmware-flash.bat @@ -62,6 +62,9 @@ echo Checking and setting arduino-cli configs... arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.data %CLI_TEMP%\data arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.downloads %CLI_TEMP%\downloads arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.user %CLI_TEMP%\user %* +rem Enable for Git installations (ie `arduino-cli lib install --git-url`). +rem @See "https://arduino.github.io/arduino-cli/0.35/configuration/#configuration-keys" +arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set library.enable_unsafe_install true echo Fetching assets... @@ -78,6 +81,9 @@ if not exist "%CLI_TEMP%\user" ( if %DATA_FLAG% gtr 0 ( arduino-cli %ARDUINO_CLI_CONFIG_FILE% core update-index arduino-cli %ARDUINO_CLI_CONFIG_FILE% core install esp32:esp32 + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/ESPAsyncWebServer.git + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/AsyncTCP.git + arduino-cli %ARDUINO_CLI_CONFIG_FILE% lib install --git-url https://github.com/me-no-dev/ESPAsyncTCP.git ) else ( echo Assets already installed. Skipping... ) @@ -158,6 +164,7 @@ echo Resetting arduino-cli config back to defaults... arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.data C:\temp\arduino-cli\data arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.downloads C:\temp\arduino-cli\staging arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set directories.user C:\temp\arduino-cli\user +arduino-cli %ARDUINO_CLI_CONFIG_FILE% config set library.enable_unsafe_install false set /p DELETE_TEMP="Would you like to delete the temporary files? (Y/N): " if /i "!DELETE_TEMP!"=="Y" ( rmdir /s /q %CLI_TEMP% diff --git a/firmware/camera.cpp b/firmware/camera.cpp index cd2861b..32b1695 100644 --- a/firmware/camera.cpp +++ b/firmware/camera.cpp @@ -9,7 +9,7 @@ void initialize_camera() { } } -void set_camera_defaults() { +void set_camera_defaults(CameraFunction camera_function) { // Get the camera sensor reference. sensor_t *cam = esp_camera_sensor_get(); @@ -19,6 +19,12 @@ void set_camera_defaults() { cam->set_sharpness(cam, 0); // Set initial sharpness. cam->set_vflip(cam, true); // Set initial vertical flip. cam->set_hmirror(cam, false); // Set initial horizontal mirror. + + if (camera_function == CAMERA_FUNCTION_SERIAL) { + // TODO + } else if (camera_function == CAMERA_FUNCTION_WIFI) { + // TODO + } } void turn_flash_off() { diff --git a/firmware/camera.h b/firmware/camera.h index fa2f727..f5362b6 100644 --- a/firmware/camera.h +++ b/firmware/camera.h @@ -11,7 +11,7 @@ void initialize_camera(); /** Reset the camera to the default settings. */ -void set_camera_defaults(); +void set_camera_defaults(CameraFunction camera_function); /** Turn the flash off. */ void turn_flash_off(); diff --git a/firmware/camera_config.cpp b/firmware/camera_config.cpp index 614c8e7..7002e85 100644 --- a/firmware/camera_config.cpp +++ b/firmware/camera_config.cpp @@ -3,7 +3,7 @@ /** The camera configuration model. */ camera_config_t camera_config; -void set_camera_config_defaults() { +void set_camera_config_defaults(CameraFunction camera_function) { camera_config.ledc_channel = LEDC_CHANNEL_0; camera_config.ledc_timer = LEDC_TIMER_0; camera_config.pin_d0 = Y2_GPIO_NUM; @@ -24,8 +24,23 @@ void set_camera_config_defaults() { camera_config.pin_reset = RESET_GPIO_NUM; camera_config.xclk_freq_hz = 20000000; - camera_config.pixel_format = PIXFORMAT_GRAYSCALE; - camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - camera_config.fb_count = 1; - camera_config.frame_size = FRAMESIZE_QQVGA; + if (camera_function == CAMERA_FUNCTION_WIFI) { + if(psramFound()){ + camera_config.fb_count = 2; + camera_config.frame_size = FRAMESIZE_UXGA; // 1600x1200 + camera_config.jpeg_quality = 10; + } else { + camera_config.fb_count = 1; + camera_config.frame_size = FRAMESIZE_SVGA; // 800x600 + camera_config.jpeg_quality = 12; + } + camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + camera_config.pixel_format = PIXFORMAT_JPEG; + } else { + camera_config.fb_count = 1; + camera_config.frame_size = FRAMESIZE_QQVGA; + camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + camera_config.jpeg_quality = 8; + camera_config.pixel_format = PIXFORMAT_GRAYSCALE; + } } diff --git a/firmware/camera_config.h b/firmware/camera_config.h index 22fef22..6fdc76a 100644 --- a/firmware/camera_config.h +++ b/firmware/camera_config.h @@ -1,6 +1,7 @@ #ifndef CAMERA_CONFIG_H #define CAMERA_CONFIG_H +#include #include #include "camera_model.h" @@ -9,7 +10,7 @@ /** The camera configuration model. */ extern camera_config_t camera_config; -/** Set the camera configuration defaults. */ -void set_camera_config_defaults(); +/** Set the camera configuration defaults based on camera function. */ +void set_camera_config_defaults(CameraFunction camera_function); #endif diff --git a/firmware/camera_model.cpp b/firmware/camera_model.cpp index 164ba07..7aec10f 100644 --- a/firmware/camera_model.cpp +++ b/firmware/camera_model.cpp @@ -3,8 +3,13 @@ /** The camera model. */ CameraModel camera_model; -void set_camera_model_defaults() { - camera_model.isDitheringEnabled = true; +void set_camera_model_defaults(CameraFunction camera_function) { + if (camera_function == CAMERA_FUNCTION_SERIAL) { + camera_model.isDitheringEnabled = true; + } else if (camera_function == CAMERA_FUNCTION_WIFI) { + camera_model.isDitheringEnabled = false; + } + camera_model.isFlashEnabled = false; camera_model.isInvertEnabled = false; camera_model.ditherAlgorithm = FLOYD_STEINBERG; diff --git a/firmware/camera_model.h b/firmware/camera_model.h index dbf741e..3d9ffad 100644 --- a/firmware/camera_model.h +++ b/firmware/camera_model.h @@ -4,13 +4,18 @@ #include /** The dithering algorithms available. */ -typedef enum { +typedef enum DitheringAlgorithm { FLOYD_STEINBERG, JARVIS_JUDICE_NINKE, STUCKI, } DitheringAlgorithm; -typedef struct { +typedef enum CameraFunction { + CAMERA_FUNCTION_SERIAL, + CAMERA_FUNCTION_WIFI, +} CameraFunction; + +typedef struct CameraModel { /** Flag to enable or disable dithering. */ bool isDitheringEnabled; /** Flag to represent the flash state when saving pictures to the Flipper Zero. */ @@ -19,6 +24,8 @@ typedef struct { bool isInvertEnabled; /** Flag to stop or start the stream to the Flipper Zero. */ bool isStreamToSerialEnabled; + /** Flag to stop or start the stream to WiFi. */ + bool isStreamToWiFiEnabled; /** Holds the currently selected dithering algorithm. */ DitheringAlgorithm ditherAlgorithm; } CameraModel; @@ -26,7 +33,7 @@ typedef struct { /** The camera model. */ extern CameraModel camera_model; -/** Set the camera model to the default values. */ -void set_camera_model_defaults(); +/** Set the camera model to the default values depending on the camera use. */ +void set_camera_model_defaults(CameraFunction camera_function); #endif diff --git a/firmware/firmware.h b/firmware/firmware.h index ed9c1b6..376ffc1 100644 --- a/firmware/firmware.h +++ b/firmware/firmware.h @@ -7,6 +7,7 @@ #include "camera_config.h" #include "camera_model.h" #include "stream_to_serial.h" +#include "stream_to_wifi.h" #include "process_serial_input.h" void setup(); diff --git a/firmware/firmware.ino b/firmware/firmware.ino index 9c73b77..483d95e 100644 --- a/firmware/firmware.ino +++ b/firmware/firmware.ino @@ -1,22 +1,23 @@ #include "firmware.h" void setup() { - camera_model.isStreamToSerialEnabled = false; + camera_model.isStreamToSerialEnabled = false; + camera_model.isStreamToWiFiEnabled = false; // Begin serial communication. Serial.begin(230400); // Set initial camera configs for serial streaming. - set_camera_config_defaults(); + set_camera_config_defaults(CAMERA_FUNCTION_SERIAL); // Set initial camera model for serial streaming. - set_camera_model_defaults(); + set_camera_model_defaults(CAMERA_FUNCTION_SERIAL); // Initialize the camera. initialize_camera(); // Set initial camera settings for serial streaming. - set_camera_defaults(); + set_camera_defaults(CAMERA_FUNCTION_SERIAL); } // Main loop of the program. @@ -26,6 +27,9 @@ void loop() { if (camera_model.isStreamToSerialEnabled) { // Process the camera image and output to serial. stream_to_serial(); + } else if (camera_model.isStreamToWiFiEnabled) { + // Stream the camera output to WiFi. + stream_to_wifi(); } else if (camera_model.isFlashEnabled) { // Not currently streaming, turn the flash off if it's enabled. turn_flash_off(); diff --git a/firmware/process_serial_input.cpp b/firmware/process_serial_input.cpp index 22e3c69..057d58e 100644 --- a/firmware/process_serial_input.cpp +++ b/firmware/process_serial_input.cpp @@ -36,12 +36,22 @@ void process_serial_input() { case 'I': set_inverted(true); break; + case 'P': + // @todo + // save_picture_to_sd_card(); + break; case 's': stop_serial_stream(); break; case 'S': start_serial_stream(); break; + case 'w': + stop_wifi_stream(); + break; + case 'W': + start_wifi_stream(); + break; case '0': set_dithering_algorithm(FLOYD_STEINBERG); break; diff --git a/firmware/process_serial_input.h b/firmware/process_serial_input.h index 6316bc2..0da95eb 100644 --- a/firmware/process_serial_input.h +++ b/firmware/process_serial_input.h @@ -7,6 +7,7 @@ #include "camera_model.h" #include "pins.h" #include "stream_to_serial.h" +#include "stream_to_wifi.h" /** Handle the serial input commands coming from the Flipper Zero. */ void process_serial_input(); diff --git a/firmware/stream_to_serial.cpp b/firmware/stream_to_serial.cpp index 92e23ff..21cf623 100644 --- a/firmware/stream_to_serial.cpp +++ b/firmware/stream_to_serial.cpp @@ -1,5 +1,6 @@ #include "stream_to_serial.h" +// Called from the main loop of the Firmware: `~firmware.ino`. void stream_to_serial() { camera_fb_t *frame_buffer = esp_camera_fb_get(); @@ -59,9 +60,15 @@ void stream_to_serial() { } void start_serial_stream() { - set_camera_config_defaults(); - set_camera_model_defaults(); - set_camera_defaults(); + // Make sure the camera is not streaming to WiFi. + camera_model.isStreamToWiFiEnabled = false; + + // Set the camera configuration, model and defaults for serial streaming. + set_camera_config_defaults(CAMERA_FUNCTION_SERIAL); + set_camera_model_defaults(CAMERA_FUNCTION_SERIAL); + set_camera_defaults(CAMERA_FUNCTION_SERIAL); + + // Enable serial streaming. camera_model.isStreamToSerialEnabled = true; } diff --git a/firmware/stream_to_wifi.cpp b/firmware/stream_to_wifi.cpp new file mode 100644 index 0000000..f9e5177 --- /dev/null +++ b/firmware/stream_to_wifi.cpp @@ -0,0 +1,84 @@ +#include "stream_to_wifi.h" + +AsyncWebServer server(80); +DNSServer dnsServer; + +char ssid[30] = "ESP"; // Default SSID +char password[30] = "12345678"; // Default Password + +bool is_wifi_streaming_initialized = false; + +// Called from the main loop of the Firmware: `~firmware.ino`. +void stream_to_wifi() { + if (!is_wifi_streaming_initialized) { + is_wifi_streaming_initialized = true; + + WiFi.mode(WIFI_AP); + WiFi.softAP(ssid, password); + + Serial.print("AP SSID: "); + Serial.println(ssid); + + dnsServer.start(53, "*", WiFi.softAPIP()); + server.begin(); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + camera_fb_t * fb = NULL; + fb = esp_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + request->send(500); + return; + } + AsyncWebServerResponse *response = + request->beginResponse( + "image/jpeg", + fb->len, + [fb](uint8_t *buffer, size_t maxLen, size_t alreadySent) -> size_t { + if(fb->len > alreadySent) { + size_t toCopy = fb->len - alreadySent; + if(toCopy > maxLen) { + toCopy = maxLen; + } + memcpy(buffer, fb->buf + alreadySent, toCopy); + return toCopy; + } + // Nothing is left to send, return 0. + return 0; + }); + response->addHeader("Connection", "close"); + request->send(response); + esp_camera_fb_return(fb); + }); + } else { + dnsServer.processNextRequest(); + } +} + +void start_wifi_stream() { + // Make sure the camera is not streaming to serial. + camera_model.isStreamToSerialEnabled = false; + + set_camera_config_defaults(CAMERA_FUNCTION_WIFI); + set_camera_model_defaults(CAMERA_FUNCTION_WIFI); + set_camera_defaults(CAMERA_FUNCTION_WIFI); + + // @todo - Dynamically set ssid and password via prompts. + + // Enable WiFi streaming. + camera_model.isStreamToWiFiEnabled = true; + + // Turn the flash on momentarily to ensure the wifi started. + // @todo - Remove after testing. + turn_flash_on(); +} + +void stop_wifi_stream() { + WiFi.softAPdisconnect(true); + camera_model.isStreamToWiFiEnabled = false; + is_wifi_streaming_initialized = false; + + // Turn the flash off after the wifi has started. + // @todo - Remove after testing. + turn_flash_off(); +} diff --git a/firmware/stream_to_wifi.h b/firmware/stream_to_wifi.h new file mode 100644 index 0000000..26f26d1 --- /dev/null +++ b/firmware/stream_to_wifi.h @@ -0,0 +1,25 @@ +#ifndef STREAM_TO_WIFI_H +#define STREAM_TO_WIFI_H + +#include +#include +#include + +#include +#include + +#include "camera.h" +#include "camera_model.h" + +#define MAX_HTML_SIZE 20000 + +/** Start the WiFi camera stream. */ +void stream_to_wifi(); + +/** Start the WiFi camera stream. */ +void start_wifi_stream(); + +/** Stop the WiFi camera stream. */ +void stop_wifi_stream(); + +#endif