hurricane/arducam/camera.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
#include "common.h"
#define CAMERA_BUFFER_LENGTH (1460*3)
#define CAMERA_SETTING(name) camera_settings->setting[ARDUCAM_SETTING_ ## name]
static struct
{
gos_msgpack_context_t *write_context;
uint32_t next_image_size;
bool enabled;
bool broadcast_settings_after_image;
} camera_context;
/*************************************************************************************************/
gos_result_t camera_init(void)
{
gos_result_t result;
const arducam_config_t arducam_config =
{
.spi.port = GOS_SPI_1,
.spi.cs = HURRICAME_CAMERA_CS_GPIO, // PB6
.i2c_port = PLATFORM_STD_I2C,
.max_read_length = CAMERA_BUFFER_LENGTH,
.driver_config =
{
.resolution = camera_settings->setting[ARDUCAM_SETTING_RESOLUTION],
.format = ARDUCAM_FORMAT_JPEG,
.jpeg_quality = camera_settings->setting[ARDUCAM_SETTING_QUALITY]
},
.callback =
{
.data_writer = arducam_data_writer_callback,
.image_ready = arducam_image_ready_callback,
.error_handler = arducam_error_callback
}
};
gos_hs_stream_register("settings", settings_stream_handler, GOS_HS_STREAM_PERMISSION_ALL, NULL);
if(GOS_FAILED(result, arducam_init(&arducam_config, ARDUCAM_TYPE_OV2640)))
{
}
else
{
arducam_set_setting(ARDUCAM_SETTING_BRIGHTNESS, CAMERA_SETTING(BRIGHTNESS));
arducam_set_setting(ARDUCAM_SETTING_CONTRAST, CAMERA_SETTING(CONTRAST));
arducam_set_setting(ARDUCAM_SETTING_SATURATION, CAMERA_SETTING(SATURATION));
arducam_set_setting(ARDUCAM_SETTING_MIRROR, CAMERA_SETTING(MIRROR));
arducam_set_setting(ARDUCAM_SETTING_FLIP, CAMERA_SETTING(FLIP));
arducam_set_setting(ARDUCAM_SETTING_SPECIALEFFECT, CAMERA_SETTING(SPECIALEFFECT));
}
return result;
}
/*************************************************************************************************/
void camera_set_video_enabled(bool enabled)
{
if(enabled && !camera_context.enabled)
{
GOS_LOG("Starting image capture");
camera_context.enabled = true;
hurricane_camera_set_led_enabled(true);
gos_event_issue(start_image_capture_handler, NULL, GOS_EVENT_FLAG_NONE);
}
else if(!enabled && camera_context.enabled)
{
GOS_LOG("Stopping image capture");
camera_context.enabled = false;
gos_event_issue(cleanup_write_context, NULL, GOS_EVENT_FLAG_IN_NETWORK_WORKER);
arducam_abort_capture();
hurricane_camera_set_led_enabled(false);
gos_event_unregister(start_image_capture_handler, NULL);
}
}
/*************************************************************************************************/
void camera_broadcast_settings_to_all_clients(void)
{
gos_event_issue(broadcast_settings_to_all_clients_event_handler, NULL, GOS_EVENT_FLAG_NONE);
}
/*************************************************************************************************
* This is called when a client wants to read or write a setting
*/
static void settings_stream_handler(const char *stream_name,
const gos_msgpack_object_t *in_data,
gos_msgpack_context_t *out_context, void *arg)
{
// If the client is updating a setting
if(in_data != NULL)
{
bool setting_updated = false;
const gos_msgpack_object_t *brightness_obj = MSGPACK_DICT_INT(in_data, "brightness");
const gos_msgpack_object_t *contrast_obj = MSGPACK_DICT_INT(in_data, "contrast");
const gos_msgpack_object_t *saturation_obj = MSGPACK_DICT_INT(in_data, "saturation");
const gos_msgpack_object_t *mirror_obj = MSGPACK_DICT_INT(in_data, "mirror");
const gos_msgpack_object_t *flip_obj = MSGPACK_DICT_INT(in_data, "flip");
const gos_msgpack_object_t *filter_obj = MSGPACK_DICT_INT(in_data, "filter");
const gos_msgpack_object_t *quality_obj = MSGPACK_DICT_INT(in_data, "quality");
const gos_msgpack_object_t *resolution_obj = MSGPACK_DICT_INT(in_data, "resolution");
setting_updated |= update_setting(ARDUCAM_SETTING_BRIGHTNESS, brightness_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_CONTRAST, contrast_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_SATURATION, saturation_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_MIRROR, mirror_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_FLIP, flip_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_SPECIALEFFECT, filter_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_QUALITY, quality_obj);
setting_updated |= update_setting(ARDUCAM_SETTING_RESOLUTION, resolution_obj);
if(setting_updated)
{
GOS_LOG("Settings updated");
}
}
// Else the client is reading the settings
else
{
send_settings(out_context);
}
}
/*************************************************************************************************/
static void broadcast_settings_to_all_clients_event_handler(void *unused)
{
gos_result_t result;
gos_msgpack_context_t *context = NULL;
// If we're actively writing an image
if(image_write_is_active())
{
// Then we need to wait to broadcast the settings
camera_context.broadcast_settings_after_image = true;
}
else if(GOS_FAILED(result, gos_hs_stream_ws_write(GOS_HS_STREAM_WRITE_ALL, "settings", &context)))
{
}
else
{
GOS_LOG("Broadcasting settings to all clients");
send_settings(context);
}
}
/*************************************************************************************************/
static void send_settings(gos_msgpack_context_t *context)
{
gos_msgpack_write_dict_int(context, "brightness", CAMERA_SETTING(BRIGHTNESS));
gos_msgpack_write_dict_int(context, "contrast", CAMERA_SETTING(CONTRAST));
gos_msgpack_write_dict_int(context, "saturation", CAMERA_SETTING(SATURATION));
gos_msgpack_write_dict_int(context, "mirror", CAMERA_SETTING(MIRROR));
gos_msgpack_write_dict_int(context, "flip", CAMERA_SETTING(FLIP));
gos_msgpack_write_dict_int(context, "filter", CAMERA_SETTING(SPECIALEFFECT));
gos_msgpack_write_dict_int(context, "quality", CAMERA_SETTING(QUALITY));
gos_msgpack_write_dict_int(context, "resolution", CAMERA_SETTING(RESOLUTION));
gos_msgpack_write_dict_int(context, "clients", client_count);
}
/*************************************************************************************************/
static bool update_setting(arducam_setting_type_t setting, const gos_msgpack_object_t *obj)
{
bool retval = false;
if(obj != NULL)
{
const int32_t value = MSGPACK_INT(obj);
if(camera_settings->setting[setting] != value)
{
if(arducam_set_setting(setting, value) == GOS_SUCCESS)
{
camera_settings->setting[setting] = value;
retval = true;
}
}
}
return retval;
}
/*************************************************************************************************
* This is called by the adrucam driver when image data is available
* It executes in the network worker
*/
static gos_result_t arducam_data_writer_callback(const void *image_chunk, uint32_t length, bool last_chunk)
{
gos_result_t result;
// If the camera is no longer enabled
if(!camera_context.enabled)
{
// Then abort
result = GOS_ABORTED;
}
// If we have a new image available
else if(camera_context.next_image_size != 0)
{
// The the image size
const uint32_t image_size = camera_context.next_image_size;
// And clear it to indicate we've allocated a write context for it
camera_context.next_image_size = 0;
// Just for good measure cleanup the old context
// Before we update the write_context pointer with a new one
cleanup_write_context(NULL);
// Allocate a new write context for it
// NOTE: This context can only be written in the network thread
if(GOS_FAILED(result, gos_hs_stream_ws_write(GOS_HS_STREAM_WRITE_ALL, "image", &camera_context.write_context)))
{
GOS_LOG("Failed to init image write context, err: %d", result);
}
else if(GOS_FAILED(result, gos_msgpack_write_bin_marker(camera_context.write_context, image_size)))
{
goto exit;
}
}
// Write the image data chunk
if(GOS_FAILED(result, gos_hs_stream_write_direct(camera_context.write_context, image_chunk, length)))
{
GOS_LOG("Failed to write image chunk, err:%d", result);
}
// If this was the last chunk
else if(last_chunk)
{
// Then flush any remaining data
if(GOS_FAILED(result, gos_hs_stream_flush(camera_context.write_context, GOS_NO_WAIT)))
{
GOS_LOG("Failed to flush last chunk, err: %d", result);
}
// Clear the pointer to the write context since it is no longer valid
camera_context.write_context = NULL;
}
exit:
// If something failed then cleanup the write context
if(result != GOS_SUCCESS)
{
arducam_abort_capture();
cleanup_write_context(NULL);
}
// If image capture is still enabled AND
// we just sent the last chunk OR
// something failed writing the previous image
if(camera_context.enabled && (last_chunk || result != GOS_SUCCESS))
{
// If we need to broadcast the settings to all clients
if(camera_context.broadcast_settings_after_image)
{
// Then do that now
camera_context.broadcast_settings_after_image = false;
camera_broadcast_settings_to_all_clients();
}
// Then start another image capture in the application thread
gos_event_issue(start_image_capture_handler, NULL, GOS_EVENT_FLAG_NONE);
}
return result;
}
/*************************************************************************************************/
static void arducam_error_callback(gos_result_t result)
{
GOS_LOG("Arducam library error: %d", result);
}
/*************************************************************************************************/
static bool arducam_image_ready_callback(uint32_t image_size)
{
// GOS_LOG("Next image size: %d", image_size);
camera_context.next_image_size = image_size;
// Return true to indicate that the image to start being read
return true;
}
/*************************************************************************************************
* Trigger an image capture
*
* This executes in the appliation thread
*/
static void start_image_capture_handler(void *unused)
{
arducam_start_capture();
}
/*************************************************************************************************
* Clean up the write context
*
* This executes in the network thread
*/
static void cleanup_write_context(void *unused)
{
if(camera_context.write_context != NULL)
{
gos_hs_stream_ws_abort_write(camera_context.write_context);
camera_context.write_context = NULL;
}
}
/*************************************************************************************************/
static inline bool image_write_is_active(void)
{
return (camera_context.write_context != NULL) ? true : false;
}