diff --git a/applications/plugins/lightmeter/LICENSE b/applications/plugins/lightmeter/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/plugins/lightmeter/README.md b/applications/plugins/lightmeter/README.md new file mode 100644 index 000000000..d9c071e67 --- /dev/null +++ b/applications/plugins/lightmeter/README.md @@ -0,0 +1,21 @@ +# flipperzero-lightmeter + +[Original link](https://github.com/oleksiikutuzov/flipperzero-lightmeter) + + + + +## Wiring + +``` +VCC -> 3.3V +GND -> GND +SCL -> C0 +SDA -> C1 +``` + +## TODO +- [ ] Save settings to sd card + +## References +App inspired by [lightmeter](https://github.com/vpominchuk/lightmeter) project for Arduino by [vpominchuk](https://github.com/vpominchuk). diff --git a/applications/plugins/lightmeter/application.fam b/applications/plugins/lightmeter/application.fam new file mode 100644 index 000000000..8cd90ee26 --- /dev/null +++ b/applications/plugins/lightmeter/application.fam @@ -0,0 +1,24 @@ +App( + appid="lightmeter", + name="[BH1750] Lightmeter", + apptype=FlipperAppType.EXTERNAL, + entry_point="lightmeter_app", + cdefines=["APP_LIGHTMETER"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=90, + fap_icon="lightmeter.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="BH1750", + cincludes=["."], + sources=[ + "BH1750.c", + ], + ), + ], + fap_icon_assets="icons", +) diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c new file mode 100644 index 000000000..2487d5817 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c @@ -0,0 +1,30 @@ +#include "lightmeter_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const lightmeter_on_enter_handlers[])(void*) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const lightmeter_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const lightmeter_on_exit_handlers[])(void* context) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers lightmeter_scene_handlers = { + .on_enter_handlers = lightmeter_on_enter_handlers, + .on_event_handlers = lightmeter_on_event_handlers, + .on_exit_handlers = lightmeter_on_exit_handlers, + .scene_num = LightMeterAppSceneNum, +}; diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h new file mode 100644 index 000000000..9d5931384 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) LightMeterAppScene##id, +typedef enum { +#include "lightmeter_scene_config.h" + LightMeterAppSceneNum, +} LightMeterAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers lightmeter_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h new file mode 100644 index 000000000..c72a7713e --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(lightmeter, main, Main) +ADD_SCENE(lightmeter, config, Config) +ADD_SCENE(lightmeter, help, Help) +ADD_SCENE(lightmeter, about, About) diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c new file mode 100644 index 000000000..1508b4c00 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c @@ -0,0 +1,71 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + LightMeterApp* app = context; + + UNUSED(app); + UNUSED(result); + UNUSED(type); + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void lightmeter_scene_about_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", LM_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", LM_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", LM_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "Showing suggested camera\nsettings based on ambient\nlight or flash.\n\nInspired by a lightmeter\nproject by vpominchuk\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Lightmeter \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewAbout); +} + +bool lightmeter_scene_about_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void lightmeter_scene_about_on_exit(void* context) { + LightMeterApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c new file mode 100644 index 000000000..42952562b --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c @@ -0,0 +1,156 @@ +#include "../../lightmeter.h" + +static const char* iso_numbers[] = { + [ISO_6] = "6", + [ISO_12] = "12", + [ISO_25] = "25", + [ISO_50] = "50", + [ISO_100] = "100", + [ISO_200] = "200", + [ISO_400] = "400", + [ISO_800] = "800", + [ISO_1600] = "1600", + [ISO_3200] = "3200", + [ISO_6400] = "6400", + [ISO_12800] = "12800", + [ISO_25600] = "25600", + [ISO_51200] = "51200", + [ISO_102400] = "102400", +}; + +static const char* nd_numbers[] = { + [ND_0] = "0", + [ND_2] = "2", + [ND_4] = "4", + [ND_8] = "8", + [ND_16] = "16", + [ND_32] = "32", + [ND_64] = "64", + [ND_128] = "128", + [ND_256] = "256", + [ND_512] = "512", + [ND_1024] = "1024", + [ND_2048] = "2048", + [ND_4096] = "4096", +}; + +static const char* diffusion_dome[] = { + [WITHOUT_DOME] = "No", + [WITH_DOME] = "Yes", +}; + +enum LightMeterSubmenuIndex { + LightMeterSubmenuIndexISO, + LightMeterSubmenuIndexND, + LightMeterSubmenuIndexDome, +}; + +static void iso_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, iso_numbers[index]); + + LightMeterConfig* config = app->config; + config->iso = index; + lightmeter_app_set_config(app, config); +} + +static void nd_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, nd_numbers[index]); + + LightMeterConfig* config = app->config; + config->nd = index; + lightmeter_app_set_config(app, config); +} + +static void dome_presence_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, diffusion_dome[index]); + + LightMeterConfig* config = app->config; + config->dome = index; + lightmeter_app_set_config(app, config); +} + +static void ok_cb(void* context, uint32_t index) { + LightMeterApp* app = context; + UNUSED(app); + switch(index) { + case 3: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventHelp); + break; + case 4: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventAbout); + break; + default: + break; + } +} + +void lightmeter_scene_config_on_enter(void* context) { + LightMeterApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + LightMeterConfig* config = app->config; + + item = + variable_item_list_add(var_item_list, "ISO", COUNT_OF(iso_numbers), iso_numbers_cb, app); + variable_item_set_current_value_index(item, config->iso); + variable_item_set_current_value_text(item, iso_numbers[config->iso]); + + item = variable_item_list_add( + var_item_list, "ND factor", COUNT_OF(nd_numbers), nd_numbers_cb, app); + variable_item_set_current_value_index(item, config->nd); + variable_item_set_current_value_text(item, nd_numbers[config->nd]); + + item = variable_item_list_add( + var_item_list, "Diffusion dome", COUNT_OF(diffusion_dome), dome_presence_cb, app); + variable_item_set_current_value_index(item, config->dome); + variable_item_set_current_value_text(item, diffusion_dome[config->dome]); + + item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, LightMeterAppSceneConfig)); + + variable_item_list_set_enter_callback(var_item_list, ok_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewVarItemList); +} + +bool lightmeter_scene_config_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case LightMeterAppCustomEventHelp: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneHelp); + consumed = true; + break; + case LightMeterAppCustomEventAbout: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneAbout); + consumed = true; + break; + } + } + return consumed; +} + +void lightmeter_scene_config_on_exit(void* context) { + LightMeterApp* app = context; + variable_item_list_reset(app->var_item_list); + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_dome(app->main_view, app->config->dome); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c new file mode 100644 index 000000000..7b6d45864 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c @@ -0,0 +1,32 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_help_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf( + temp_str, "App works with BH1750\nambient light sensor\nconnected via I2C interface\n\n"); + furi_string_cat(temp_str, "\e#Pinout:\r\n"); + furi_string_cat( + temp_str, + " SDA: 15 [C1]\r\n" + " SCL: 16 [C0]\r\n"); + + widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewHelp); +} + +bool lightmeter_scene_help_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void lightmeter_scene_help_on_exit(void* context) { + LightMeterApp* app = context; + + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c new file mode 100644 index 000000000..8ba474461 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c @@ -0,0 +1,43 @@ +#include "../../lightmeter.h" + +static void lightmeter_scene_main_on_left(void* context) { + LightMeterApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventConfig); +} + +void lightmeter_scene_main_on_enter(void* context) { + LightMeterApp* app = context; + + lightmeter_main_view_set_left_callback(app->main_view, lightmeter_scene_main_on_left, app); + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewMainView); +} + +bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool response = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + if(event.event == LightMeterAppCustomEventConfig) { + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneConfig); + response = true; + } + break; + + case SceneManagerEventTypeTick: + lightmeter_app_i2c_callback(app); + response = true; + break; + + default: + break; + } + + return response; +} + +void lightmeter_scene_main_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.c b/applications/plugins/lightmeter/gui/views/main_view.c new file mode 100644 index 000000000..756346fa4 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.c @@ -0,0 +1,434 @@ +#include "main_view.h" +#include +#include +#include +#include "../../lightmeter.h" +#include "../../lightmeter_helper.h" + +#define WORKER_TAG "Main View" + +static const int iso_numbers[] = { + [ISO_6] = 6, + [ISO_12] = 12, + [ISO_25] = 25, + [ISO_50] = 50, + [ISO_100] = 100, + [ISO_200] = 200, + [ISO_400] = 400, + [ISO_800] = 800, + [ISO_1600] = 1600, + [ISO_3200] = 3200, + [ISO_6400] = 6400, + [ISO_12800] = 12800, + [ISO_25600] = 25600, + [ISO_51200] = 51200, + [ISO_102400] = 102400, +}; + +static const int nd_numbers[] = { + [ND_0] = 0, + [ND_2] = 2, + [ND_4] = 4, + [ND_8] = 8, + [ND_16] = 16, + [ND_32] = 32, + [ND_64] = 64, + [ND_128] = 128, + [ND_256] = 256, + [ND_512] = 512, + [ND_1024] = 1024, + [ND_2048] = 2048, + [ND_4096] = 4096, +}; + +static const float aperture_numbers[] = { + [AP_1] = 1.0, + [AP_1_4] = 1.4, + [AP_2] = 2.0, + [AP_2_8] = 2.8, + [AP_4] = 4.0, + [AP_5_6] = 5.6, + [AP_8] = 8, + [AP_11] = 11, + [AP_16] = 16, + [AP_22] = 22, + [AP_32] = 32, + [AP_45] = 45, + [AP_64] = 64, + [AP_90] = 90, + [AP_128] = 128, +}; + +static const float speed_numbers[] = { + [SPEED_8000] = 1.0 / 8000, [SPEED_4000] = 1.0 / 4000, [SPEED_2000] = 1.0 / 2000, + [SPEED_1000] = 1.0 / 1000, [SPEED_500] = 1.0 / 500, [SPEED_250] = 1.0 / 250, + [SPEED_125] = 1.0 / 125, [SPEED_60] = 1.0 / 60, [SPEED_30] = 1.0 / 30, + [SPEED_15] = 1.0 / 15, [SPEED_8] = 1.0 / 8, [SPEED_4] = 1.0 / 4, + [SPEED_2] = 1.0 / 2, [SPEED_1S] = 1.0, [SPEED_2S] = 2.0, + [SPEED_4S] = 4.0, [SPEED_8S] = 8.0, [SPEED_15S] = 15.0, + [SPEED_30S] = 30.0, +}; + +struct MainView { + View* view; + LightMeterMainViewButtonCallback cb_left; + void* cb_context; +}; + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context) { + with_view_model( + lightmeter_main_view->view, + MainViewModel * model, + { + UNUSED(model); + lightmeter_main_view->cb_left = callback; + lightmeter_main_view->cb_context = context; + }, + true); +} + +static void main_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + MainViewModel* model = context; + + // FURI_LOG_D("MAIN VIEW", "Drawing"); + + canvas_clear(canvas); + + // top row + draw_top_row(canvas, model); + + // add f, T values + canvas_set_font(canvas, FontBigNumbers); + + // draw f icon and number + canvas_draw_icon(canvas, 15, 17, &I_f_10x14); + draw_aperture(canvas, model); + + // draw T icon and number + canvas_draw_icon(canvas, 15, 34, &I_T_10x14); + draw_speed(canvas, model); + + // draw button + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Config"); + + // draw ND number + draw_nd_number(canvas, model); + + // draw EV number + canvas_set_font(canvas, FontSecondary); + draw_EV_number(canvas, model); + + // draw mode indicator + draw_mode_indicator(canvas, model); +} + +static void main_view_process(MainView* main_view, InputEvent* event) { + with_view_model( + main_view->view, + MainViewModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture < AP_NUM - 1) model->aperture++; + break; + + case FIXED_SPEED: + if(model->speed < SPEED_NUM - 1) model->speed++; + break; + + default: + break; + } + } else if(event->key == InputKeyDown) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture > 0) model->aperture--; + break; + + case FIXED_SPEED: + if(model->speed > 0) model->speed--; + break; + + default: + break; + } + } else if(event->key == InputKeyOk) { + switch(model->current_mode) { + case FIXED_SPEED: + model->current_mode = FIXED_APERTURE; + break; + + case FIXED_APERTURE: + model->current_mode = FIXED_SPEED; + break; + + default: + break; + } + } + } + }, + true); +} + +static bool main_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + MainView* main_view = context; + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyLeft) { + if(main_view->cb_left) { + main_view->cb_left(main_view->cb_context); + } + consumed = true; + } else if(event->type == InputTypeShort && event->key == InputKeyBack) { + } else { + main_view_process(main_view, event); + consumed = true; + } + + return consumed; +} + +MainView* main_view_alloc() { + MainView* main_view = malloc(sizeof(MainView)); + main_view->view = view_alloc(); + view_set_context(main_view->view, main_view); + view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(MainViewModel)); + view_set_draw_callback(main_view->view, main_view_draw_callback); + view_set_input_callback(main_view->view, main_view_input_callback); + + return main_view; +} + +void main_view_free(MainView* main_view) { + furi_assert(main_view); + view_free(main_view->view); + free(main_view); +} + +View* main_view_get_view(MainView* main_view) { + furi_assert(main_view); + return main_view->view; +} + +void main_view_set_lux(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->lux = val; }, true); +} + +void main_view_set_EV(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->EV = val; }, true); +} + +void main_view_set_response(MainView* main_view, bool val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->response = val; }, true); +} + +void main_view_set_iso(MainView* main_view, int iso) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->iso = iso; }, true); +} + +void main_view_set_nd(MainView* main_view, int nd) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->nd = nd; }, true); +} + +void main_view_set_aperture(MainView* main_view, int aperture) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->aperture = aperture; }, true); +} + +void main_view_set_speed(MainView* main_view, int speed) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->speed = speed; }, true); +} + +void main_view_set_dome(MainView* main_view, bool dome) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->dome = dome; }, true); +} + +bool main_view_get_dome(MainView* main_view) { + furi_assert(main_view); + bool val = false; + with_view_model( + main_view->view, MainViewModel * model, { val = model->dome; }, true); + return val; +} + +void draw_top_row(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + if(!model->response) { + canvas_draw_box(canvas, 0, 0, 128, 12); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 24, 10, "No sensor found"); + canvas_set_color(canvas, ColorBlack); + } else { + model->iso_val = iso_numbers[model->iso]; + if(model->nd > 0) model->iso_val /= nd_numbers[model->nd]; + + if(model->lux > 0) { + if(model->current_mode == FIXED_APERTURE) { + model->speed_val = 100 * pow(aperture_numbers[model->aperture], 2) / + (double)model->iso_val / pow(2, model->EV); + } else { + model->aperture_val = sqrt( + pow(2, model->EV) * (double)model->iso_val * + (double)speed_numbers[model->speed] / 100); + } + } + + // TODO when T:30, f/0 instead of f/128 + + canvas_draw_line(canvas, 0, 10, 128, 10); + + canvas_set_font(canvas, FontPrimary); + // metering mode A – ambient, F – flash + canvas_draw_str_aligned(canvas, 1, 1, AlignLeft, AlignTop, "A"); + + snprintf(str, sizeof(str), "ISO: %d", iso_numbers[model->iso]); + canvas_draw_str_aligned(canvas, 19, 1, AlignLeft, AlignTop, str); + + canvas_set_font(canvas, FontSecondary); + snprintf(str, sizeof(str), "lx: %.0f", (double)model->lux); + canvas_draw_str_aligned(canvas, 87, 2, AlignLeft, AlignTop, str); + } +} + +void draw_aperture(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->response) { + if(model->aperture < AP_8) { + snprintf(str, sizeof(str), "/%.1f", (double)aperture_numbers[model->aperture]); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)aperture_numbers[model->aperture]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + case FIXED_SPEED: + if(model->aperture_val < aperture_numbers[0] || !model->response) { + snprintf(str, sizeof(str), " ---"); + } else if(model->aperture_val < aperture_numbers[AP_8]) { + snprintf(str, sizeof(str), "/%.1f", (double)normalizeAperture(model->aperture_val)); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)normalizeAperture(model->aperture_val)); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + default: + break; + } +} + +void draw_speed(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->lux > 0 && model->response) { + if(model->speed_val < 1 && model->speed_val > 0) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)normalizeTime(model->speed_val)); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)normalizeTime(model->speed_val)); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + case FIXED_SPEED: + if(model->response) { + if(model->speed < SPEED_1S) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)speed_numbers[model->speed]); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)speed_numbers[model->speed]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + default: + break; + } +} + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + switch(model->current_mode) { + case FIXED_SPEED: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 36, AlignLeft, AlignTop, "*"); + break; + + case FIXED_APERTURE: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 17, AlignLeft, AlignTop, "*"); + break; + + default: + break; + } +} + +void draw_nd_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[9]; + + if(model->response) { + snprintf(str, sizeof(str), "ND: %d", nd_numbers[model->nd]); + } else { + snprintf(str, sizeof(str), "ND: ---"); + } + canvas_draw_str_aligned(canvas, 87, 20, AlignLeft, AlignBottom, str); +} + +void draw_EV_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[7]; + + if(model->lux > 0 && model->response) { + snprintf(str, sizeof(str), "EV: %1.0f", (double)model->EV); + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, str); + } else { + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, "EV: --"); + } +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.h b/applications/plugins/lightmeter/gui/views/main_view.h new file mode 100644 index 000000000..4586e6a54 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include "lightmeter_icons.h" +#include "../../lightmeter_config.h" + +typedef struct MainView MainView; + +typedef enum { + FIXED_APERTURE, + FIXED_SPEED, + + MODES_SIZE +} MainViewMode; + +typedef struct { + uint8_t recv[2]; + MainViewMode current_mode; + float lux; + float EV; + float aperture_val; + float speed_val; + int iso_val; + bool response; + int iso; + int nd; + int aperture; + int speed; + bool dome; +} MainViewModel; + +typedef void (*LightMeterMainViewButtonCallback)(void* context); + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context); + +MainView* main_view_alloc(); + +void main_view_free(MainView* main_view); + +View* main_view_get_view(MainView* main_view); + +void main_view_set_lux(MainView* main_view, float val); + +void main_view_set_EV(MainView* main_view_, float val); + +void main_view_set_response(MainView* main_view_, bool val); + +void main_view_set_iso(MainView* main_view, int val); + +void main_view_set_nd(MainView* main_view, int val); + +void main_view_set_aperture(MainView* main_view, int val); + +void main_view_set_speed(MainView* main_view, int val); + +void main_view_set_dome(MainView* main_view, bool val); + +bool main_view_get_dome(MainView* main_view); + +void draw_top_row(Canvas* canvas, MainViewModel* context); + +void draw_aperture(Canvas* canvas, MainViewModel* context); + +void draw_speed(Canvas* canvas, MainViewModel* context); + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context); + +void draw_nd_number(Canvas* canvas, MainViewModel* context); + +void draw_EV_number(Canvas* canvas, MainViewModel* context); diff --git a/applications/plugins/lightmeter/icons/T_10x14.png b/applications/plugins/lightmeter/icons/T_10x14.png new file mode 100644 index 000000000..d81c2c424 Binary files /dev/null and b/applications/plugins/lightmeter/icons/T_10x14.png differ diff --git a/applications/plugins/lightmeter/icons/f_10x14.png b/applications/plugins/lightmeter/icons/f_10x14.png new file mode 100644 index 000000000..c3e85c0ec Binary files /dev/null and b/applications/plugins/lightmeter/icons/f_10x14.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui.gif b/applications/plugins/lightmeter/images/framed_gui.gif new file mode 100644 index 000000000..86c4d79a5 Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui.gif differ diff --git a/applications/plugins/lightmeter/images/framed_gui_config.png b/applications/plugins/lightmeter/images/framed_gui_config.png new file mode 100644 index 000000000..3d0f0c88a Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_config.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui_main.png b/applications/plugins/lightmeter/images/framed_gui_main.png new file mode 100644 index 000000000..89aa1a11f Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_main.png differ diff --git a/applications/plugins/lightmeter/images/gui_config.png b/applications/plugins/lightmeter/images/gui_config.png new file mode 100644 index 000000000..ac7de4517 Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_config.png differ diff --git a/applications/plugins/lightmeter/images/gui_main.png b/applications/plugins/lightmeter/images/gui_main.png new file mode 100644 index 000000000..ae523aa2f Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_main.png differ diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.c b/applications/plugins/lightmeter/lib/BH1750/BH1750.c new file mode 100644 index 000000000..28616e040 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.c @@ -0,0 +1,144 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include "BH1750.h" + +BH1750_mode bh1750_mode = BH1750_DEFAULT_MODE; // Current sensor mode +uint8_t bh1750_mt_reg = BH1750_DEFAULT_MTREG; // Current MT register value + +BH1750_STATUS bh1750_init() { + if(BH1750_OK == bh1750_reset()) { + if(BH1750_OK == bh1750_set_mt_reg(BH1750_DEFAULT_MTREG)) { + return BH1750_OK; + } + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_reset() { + uint8_t command = 0x07; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &command, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn) { + PowerOn = (PowerOn ? 1 : 0); + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &PowerOn, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mode(BH1750_mode mode) { + if(!((mode >> 4) || (mode >> 5))) { + return BH1750_ERROR; + } + + if((mode & 0x0F) > 3) { + return BH1750_ERROR; + } + + bool status; + + bh1750_mode = mode; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &mode, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mt_reg(uint8_t mt_reg) { + if(mt_reg < 31 || mt_reg > 254) { + return BH1750_ERROR; + } + + bh1750_mt_reg = mt_reg; + + uint8_t tmp[2]; + bool status; + + tmp[0] = (0x40 | (mt_reg >> 5)); + tmp[1] = (0x60 | (mt_reg & 0x1F)); + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[0], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(!status) { + return BH1750_ERROR; + } + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[1], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_trigger_manual_conversion() { + if(BH1750_OK == bh1750_set_mode(bh1750_mode)) { + return BH1750_OK; + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_read_light(float* result) { + float result_tmp; + uint8_t rcv[2]; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_rx(I2C_BUS, BH1750_ADDRESS, rcv, 2, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + result_tmp = (rcv[0] << 8) | (rcv[1]); + + if(bh1750_mt_reg != BH1750_DEFAULT_MTREG) { + result_tmp *= (float)((uint8_t)BH1750_DEFAULT_MTREG / (float)bh1750_mt_reg); + } + + if(bh1750_mode == ONETIME_HIGH_RES_MODE_2 || bh1750_mode == CONTINUOUS_HIGH_RES_MODE_2) { + result_tmp /= 2.0; + } + + *result = result_tmp / BH1750_CONVERSION_FACTOR; + + return BH1750_OK; + } + return BH1750_ERROR; +} diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.h b/applications/plugins/lightmeter/lib/BH1750/BH1750.h new file mode 100644 index 000000000..991350f7f --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.h @@ -0,0 +1,103 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include +#include + +#ifndef BH1750_H_ +#define BH1750_H_ + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 10 + +#define BH1750_ADDRESS (0x23 << 1) + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_POWER_ON 0x01 +#define BH1750_RESET 0x07 +#define BH1750_DEFAULT_MTREG 69 +#define BH1750_DEFAULT_MODE ONETIME_HIGH_RES_MODE + +#define BH1750_CONVERSION_FACTOR 1.2 + +typedef enum { BH1750_OK = 0, BH1750_ERROR = 1 } BH1750_STATUS; + +typedef enum { + CONTINUOUS_HIGH_RES_MODE = 0x10, + CONTINUOUS_HIGH_RES_MODE_2 = 0x11, + CONTINUOUS_LOW_RES_MODE = 0x13, + ONETIME_HIGH_RES_MODE = 0x20, + ONETIME_HIGH_RES_MODE_2 = 0x21, + ONETIME_LOW_RES_MODE = 0x23 +} BH1750_mode; + +/** + * @brief Initialize the sensor. Sends the reset command and sets the measurement register to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_init(); + +/** + * @brief Reset all registers to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_reset(); + +/** + * @brief Sets the power state. 1 - running; 0 - sleep, low power. + * + * @param PowerOn sensor state. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn); + +/** + * @brief Set the Measurement Time register. It allows to increase or decrease the sensitivity. + * + * @param MTreg value from 31 to 254, defaults to 69. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mt_reg(uint8_t MTreg); + +/** + * @brief Set the mode of converting. Look into the bh1750_mode enum. + * + * @param Mode mode enumerator + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mode(BH1750_mode Mode); + +/** + * @brief Trigger the conversion in manual modes. + * + * @details a low-resolution mode, the conversion time is typically 16 ms, and for a high-resolution + * mode is 120 ms. You need to wait until reading the measurement value. There is no need + * to exit low-power mode for manual conversion. It makes automatically. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_trigger_manual_conversion(); + +/** + * @brief Read the converted value and calculate the result. + * + * @param Result stores received value to this variable. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_read_light(float* Result); + +#endif /* BH1750_H_ */ diff --git a/applications/plugins/lightmeter/lib/BH1750/LICENSE b/applications/plugins/lightmeter/lib/BH1750/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/plugins/lightmeter/lib/BH1750/README.md b/applications/plugins/lightmeter/lib/BH1750/README.md new file mode 100644 index 000000000..b1338d4ab --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/README.md @@ -0,0 +1,2 @@ +# flipperzero-BH1750 +BH1750 light sensor library for Flipper Zero diff --git a/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf new file mode 100644 index 000000000..267efddc6 Binary files /dev/null and b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf differ diff --git a/applications/plugins/lightmeter/lightmeter.c b/applications/plugins/lightmeter/lightmeter.c new file mode 100644 index 000000000..6034a9ee9 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.c @@ -0,0 +1,161 @@ +#include "lightmeter.h" +#include "lightmeter_helper.h" + +#define WORKER_TAG "MAIN APP" + +static bool lightmeter_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool lightmeter_back_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_back_event(app->scene_manager); +} + +static void lightmeter_tick_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + scene_manager_handle_tick_event(app->scene_manager); +} + +LightMeterApp* lightmeter_app_alloc(uint32_t first_scene) { + LightMeterApp* app = malloc(sizeof(LightMeterApp)); + + // Sensor + bh1750_set_power_state(1); + bh1750_init(); + bh1750_set_mode(ONETIME_HIGH_RES_MODE); + bh1750_set_mt_reg(100); + + // Set default values to config + app->config = malloc(sizeof(LightMeterConfig)); + app->config->iso = DEFAULT_ISO; + app->config->nd = DEFAULT_ND; + app->config->aperture = DEFAULT_APERTURE; + app->config->dome = DEFAULT_DOME; + + // Records + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + notification_message( + app->notifications, &sequence_display_backlight_enforce_on); // force on backlight + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&lightmeter_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, lightmeter_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, lightmeter_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, lightmeter_tick_event_callback, furi_ms_to_ticks(200)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->main_view = main_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewMainView, main_view_get_view(app->main_view)); + + // Set default values to main view from config + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_aperture(app->main_view, app->config->aperture); + main_view_set_speed(app->main_view, DEFAULT_SPEED); + main_view_set_dome(app->main_view, app->config->dome); + + // Variable item list + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + LightMeterAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewAbout, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewHelp, widget_get_view(app->widget)); + + // Set first scene + scene_manager_next_scene(app->scene_manager, first_scene); + return app; +} + +void lightmeter_app_free(LightMeterApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewMainView); + main_view_free(app->main_view); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewVarItemList); + variable_item_list_free(app->var_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewAbout); + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewHelp); + widget_free(app->widget); + + // View dispatcher + scene_manager_free(app->scene_manager); + view_dispatcher_free(app->view_dispatcher); + + // Records + furi_record_close(RECORD_GUI); + notification_message( + app->notifications, + &sequence_display_backlight_enforce_auto); // set backlight back to auto + furi_record_close(RECORD_NOTIFICATION); + + bh1750_set_power_state(0); + + free(app->config); + free(app); +} + +int32_t lightmeter_app(void* p) { + UNUSED(p); + uint32_t first_scene = LightMeterAppSceneMain; + LightMeterApp* app = lightmeter_app_alloc(first_scene); + view_dispatcher_run(app->view_dispatcher); + lightmeter_app_free(app); + return 0; +} + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config) { + LightMeterApp* app = context; + + app->config = config; +} + +void lightmeter_app_i2c_callback(LightMeterApp* context) { + LightMeterApp* app = context; + + float EV = 0; + float lux = 0; + bool response = 0; + + if(bh1750_trigger_manual_conversion() == BH1750_OK) response = 1; + + if(response) { + bh1750_read_light(&lux); + + if(main_view_get_dome(app->main_view)) lux *= DOME_COEFFICIENT; + + EV = lux2ev(lux); + } + + main_view_set_lux(app->main_view, lux); + main_view_set_EV(app->main_view, EV); + main_view_set_response(app->main_view, response); +} diff --git a/applications/plugins/lightmeter/lightmeter.h b/applications/plugins/lightmeter/lightmeter.h new file mode 100644 index 000000000..679b32d15 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "gui/views/main_view.h" + +#include +#include + +#include "gui/scenes/config/lightmeter_scene.h" +#include + +#include "lightmeter_config.h" +#include + +typedef struct { + int iso; + int nd; + int aperture; + int dome; +} LightMeterConfig; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + MainView* main_view; + VariableItemList* var_item_list; + LightMeterConfig* config; + NotificationApp* notifications; + Widget* widget; +} LightMeterApp; + +typedef enum { + LightMeterAppViewMainView, + LightMeterAppViewConfigView, + LightMeterAppViewVarItemList, + LightMeterAppViewAbout, + LightMeterAppViewHelp, +} LightMeterAppView; + +typedef enum { + LightMeterAppCustomEventConfig, + LightMeterAppCustomEventHelp, + LightMeterAppCustomEventAbout, +} LightMeterAppCustomEvent; + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config); + +void lightmeter_app_i2c_callback(LightMeterApp* context); diff --git a/applications/plugins/lightmeter/lightmeter.png b/applications/plugins/lightmeter/lightmeter.png new file mode 100644 index 000000000..cacd2276f Binary files /dev/null and b/applications/plugins/lightmeter/lightmeter.png differ diff --git a/applications/plugins/lightmeter/lightmeter_config.h b/applications/plugins/lightmeter/lightmeter_config.h new file mode 100644 index 000000000..023235cff --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_config.h @@ -0,0 +1,99 @@ +#pragma once + +#define LM_VERSION_APP "0.5" +#define LM_DEVELOPED "Oleksii Kutuzov" +#define LM_GITHUB "https://github.com/oleksiikutuzov/flipperzero-lightmeter" + +#define DOME_COEFFICIENT 2.3 +#define DEFAULT_ISO ISO_100 +#define DEFAULT_ND ND_0 +#define DEFAULT_APERTURE AP_2_8 +#define DEFAULT_SPEED SPEED_125 +#define DEFAULT_DOME WITHOUT_DOME + +typedef enum { + ISO_6, + ISO_12, + ISO_25, + ISO_50, + ISO_100, + ISO_200, + ISO_400, + ISO_800, + ISO_1600, + ISO_3200, + ISO_6400, + ISO_12800, + ISO_25600, + ISO_51200, + ISO_102400, + + ISO_NUM, +} LightMeterISONumbers; + +typedef enum { + ND_0, + ND_2, + ND_4, + ND_8, + ND_16, + ND_32, + ND_64, + ND_128, + ND_256, + ND_512, + ND_1024, + ND_2048, + ND_4096, + + ND_NUM, +} LightMeterNDNumbers; + +typedef enum { + AP_1, + AP_1_4, + AP_2, + AP_2_8, + AP_4, + AP_5_6, + AP_8, + AP_11, + AP_16, + AP_22, + AP_32, + AP_45, + AP_64, + AP_90, + AP_128, + + AP_NUM, +} LightMeterApertureNumbers; + +typedef enum { + SPEED_8000, + SPEED_4000, + SPEED_2000, + SPEED_1000, + SPEED_500, + SPEED_250, + SPEED_125, + SPEED_60, + SPEED_30, + SPEED_15, + SPEED_8, + SPEED_4, + SPEED_2, + SPEED_1S, + SPEED_2S, + SPEED_4S, + SPEED_8S, + SPEED_15S, + SPEED_30S, + + SPEED_NUM, +} LightMeterSpeedNumbers; + +typedef enum { + WITHOUT_DOME, + WITH_DOME, +} LightMeterDomePresence; diff --git a/applications/plugins/lightmeter/lightmeter_helper.c b/applications/plugins/lightmeter/lightmeter_helper.c new file mode 100644 index 000000000..1cdddfca9 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.c @@ -0,0 +1,69 @@ +#include "lightmeter_helper.h" +#include "lightmeter_config.h" + +static const float aperture_numbers[] = { + [AP_1] = 1.0, + [AP_1_4] = 1.4, + [AP_2] = 2.0, + [AP_2_8] = 2.8, + [AP_4] = 4.0, + [AP_5_6] = 5.6, + [AP_8] = 8, + [AP_11] = 11, + [AP_16] = 16, + [AP_22] = 22, + [AP_32] = 32, + [AP_45] = 45, + [AP_64] = 64, + [AP_90] = 90, + [AP_128] = 128, +}; + +static const float time_numbers[] = { + [SPEED_8000] = 1.0 / 8000, [SPEED_4000] = 1.0 / 4000, [SPEED_2000] = 1.0 / 2000, + [SPEED_1000] = 1.0 / 1000, [SPEED_500] = 1.0 / 500, [SPEED_250] = 1.0 / 250, + [SPEED_125] = 1.0 / 125, [SPEED_60] = 1.0 / 60, [SPEED_30] = 1.0 / 30, + [SPEED_15] = 1.0 / 15, [SPEED_8] = 1.0 / 8, [SPEED_4] = 1.0 / 4, + [SPEED_2] = 1.0 / 2, [SPEED_1S] = 1.0, [SPEED_2S] = 2.0, + [SPEED_4S] = 4.0, [SPEED_8S] = 8.0, [SPEED_15S] = 15.0, + [SPEED_30S] = 30.0, +}; + +float lux2ev(float lux) { + return log2(lux / 2.5); +} + +float getMinDistance(float x, float v1, float v2) { + if(x - v1 > v2 - x) { + return v2; + } + + return v1; +} + +// Convert calculated aperture value to photography style aperture value. +float normalizeAperture(float a) { + for(int i = 0; i < AP_NUM; i++) { + float a1 = aperture_numbers[i]; + float a2 = aperture_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} + +float normalizeTime(float a) { + for(int i = 0; i < SPEED_NUM; i++) { + float a1 = time_numbers[i]; + float a2 = time_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} diff --git a/applications/plugins/lightmeter/lightmeter_helper.h b/applications/plugins/lightmeter/lightmeter_helper.h new file mode 100644 index 000000000..78ea6a8d8 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +float lux2ev(float lux); + +float getMinDistance(float x, float v1, float v2); + +float normalizeAperture(float a); + +float normalizeTime(float a);