Plugins: Add lightmeter

https://github.com/oleksiikutuzov/flipperzero-lightmeter
This commit is contained in:
MX 2022-11-12 23:13:09 +03:00
parent de5eb16ef2
commit 1b64a95ec6
No known key found for this signature in database
GPG key ID: 6C4C311DFD4B4AB5
30 changed files with 1604 additions and 0 deletions

View file

@ -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.

View file

@ -0,0 +1,21 @@
# flipperzero-lightmeter
[Original link](https://github.com/oleksiikutuzov/flipperzero-lightmeter)
<img src="images/framed_gui_main.png" width="500px">
## 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).

View file

@ -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",
)

View file

@ -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,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// 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

View file

@ -0,0 +1,4 @@
ADD_SCENE(lightmeter, main, Main)
ADD_SCENE(lightmeter, config, Config)
ADD_SCENE(lightmeter, help, Help)
ADD_SCENE(lightmeter, about, About)

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -0,0 +1,434 @@
#include "main_view.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#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: --");
}
}

View file

@ -0,0 +1,73 @@
#pragma once
#include <gui/view.h>
#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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -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;
}

View file

@ -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 <furi.h>
#include <furi_hal.h>
#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_ */

View file

@ -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.

View file

@ -0,0 +1,2 @@
# flipperzero-BH1750
BH1750 light sensor library for Flipper Zero

View file

@ -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);
}

View file

@ -0,0 +1,56 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include "gui/views/main_view.h"
#include <gui/modules/widget.h>
#include <gui/modules/variable_item_list.h>
#include "gui/scenes/config/lightmeter_scene.h"
#include <notification/notification_messages.h>
#include "lightmeter_config.h"
#include <BH1750.h>
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

View file

@ -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;

View file

@ -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;
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <math.h>
float lux2ev(float lux);
float getMinDistance(float x, float v1, float v2);
float normalizeAperture(float a);
float normalizeTime(float a);