Просмотр исходного кода

config on SD card - read/save, change meter resolution, add stats in lux_only mode (#39)

* add Measurement Resolution setting

* load settings on main_view enter, add peak lux stat

* very simple histogram

* add todo for later

* save/read settings to SD card

* fix config path and button rendering

* increase stack size

convert `flipper_format_*_hex` to `flipper_format_*_int32`

* proper `bh1750_set_mode` on app init

- move `bh1750_set_mode` after config is loaded
- replace hardcoded value with `app->config->measurement_resolution`

* add I2C addr selection, fix sensor init

single callback function that handles sensor mode and address setting

this requires https://github.com/oleksiikutuzov/flipperzero-BH1750/pull/1 to be merged

* bump lib to origin

* inscreasing stack size to be safe

* Revert "Merge branch 'main' into advanced-mode"

This reverts commit 0c0d0b9655310dabb100512ad7bdf23073e9b9e7, reversing
changes made to 7998c32a6941aa1ead4f71d020575979cd139d36.

* Fix conflicts

* More files to the state of the main branch

* Fix formatting

* Update changelog

* Write sensor type to config, set address for MAX sensor

* Switching address for both sensors

* Use shortcut for appdata folder, update changelog

* Update license to current state

* Update docs

* Update screenshots

---------

Co-authored-by: Oleksii Kutuzov <8535871+oleksiikutuzov@users.noreply.github.com>
Co-authored-by: Oleksii Kutuzov <oleksii.kutuzov@icloud.com>
Daniel Skowroński 2 лет назад
Родитель
Сommit
8a1ccc47e7

+ 191 - 0
.clang-format

@@ -0,0 +1,191 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth:   -1
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        c++03
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

BIN
.flipcorg/gallery/gui_config_2.png


BIN
.flipcorg/gallery/gui_lux_meter.png


+ 2 - 2
application.fam

@@ -7,9 +7,9 @@ App(
     requires=[
         "gui",
     ],
-    stack_size=1 * 1024,
+    stack_size= 4 * 1024,
     order=90,
-    fap_version=(1, 1),
+    fap_version=(1, 2),
     fap_icon="lightmeter.png",
     fap_category="GPIO",
     fap_private_libs=[

+ 0 - 0
README.md → docs/README.md


+ 8 - 0
docs/changelog.md

@@ -1,3 +1,11 @@
+## v1.2
+
+* Lux only screen now has statistics
+* Settings are now stored on SD card
+* You can choose the resolution (BH1750 only) and address for sensor
+
+(thanks to @danielskowronski for contributing to this update)
+
 ## v1.1
 
 Added support for MAX44009 sensor (thanks to @wosk)

+ 106 - 0
gui/scenes/lightmeter_scene_config.c

@@ -56,6 +56,22 @@ static const char* sensor_type[] = {
     [SENSOR_MAX44009] = "MAX44009",
 };
 
+static const char* measurement_resolution[] = {
+    [LOW_RES] = "Low",
+    [HIGH_RES] = "High",
+    [HIGH_RES2] = "High2",
+};
+
+static const char* device_addr_bh1750[] = {
+    [ADDR_LOW] = "0x23",
+    [ADDR_HIGH] = "0x5C",
+};
+
+static const char* device_addr_max44009[] = {
+    [ADDR_LOW] = "0x4A",
+    [ADDR_HIGH] = "0x4B",
+};
+
 enum LightMeterSubmenuIndex {
     LightMeterSubmenuIndexISO,
     LightMeterSubmenuIndexND,
@@ -63,6 +79,8 @@ enum LightMeterSubmenuIndex {
     LightMeterSubmenuIndexBacklight,
     LightMeterSubmenuIndexLuxMeter,
     LightMeterSubmenuIndexSensorType,
+    LightMeterSubmenuIndexMeasurementResolution,
+    LightMeterSubmenuIndexI2CAddress,
     LightMeterSubmenuIndexHelp,
     LightMeterSubmenuIndexAbout,
 };
@@ -133,6 +151,60 @@ static void lux_only_cb(VariableItem* item) {
     lightmeter_app_set_config(app, config);
 }
 
+static void measurement_resolution_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, measurement_resolution[index]);
+
+    LightMeterConfig* config = app->config;
+    config->measurement_resolution = index;
+    lightmeter_app_set_config(app, config);
+
+    lightmeter_app_i2c_init_sensor(app);
+}
+
+static void update_item_addr(LightMeterApp* app) {
+    VariableItem* item = app->var_item_addr;
+    switch(app->config->sensor_type) {
+    case SENSOR_BH1750:
+        variable_item_set_current_value_index(item, app->config->device_addr);
+        variable_item_set_current_value_text(item, device_addr_bh1750[app->config->device_addr]);
+        break;
+    case SENSOR_MAX44009:
+        variable_item_set_current_value_index(item, app->config->device_addr);
+        variable_item_set_current_value_text(item, device_addr_max44009[app->config->device_addr]);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Invalid sensor type %ld", app->config->sensor_type);
+        return;
+    }
+}
+
+static void device_addr_cb(VariableItem* item) {
+    LightMeterApp* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    switch(app->config->sensor_type) {
+    case SENSOR_BH1750:
+        variable_item_set_current_value_text(item, device_addr_bh1750[index]);
+        break;
+    case SENSOR_MAX44009:
+        variable_item_set_current_value_text(item, device_addr_max44009[index]);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Invalid sensor type %ld", app->config->sensor_type);
+        return;
+    }
+    // variable_item_set_current_value_text(item, device_addr[index]);
+
+    LightMeterConfig* config = app->config;
+    config->device_addr = index;
+    lightmeter_app_set_config(app, config);
+
+    lightmeter_app_i2c_init_sensor(app);
+}
+
 static void sensor_type_cb(VariableItem* item) {
     LightMeterApp* app = variable_item_get_context(item);
     uint8_t index = variable_item_get_current_value_index(item);
@@ -141,6 +213,9 @@ static void sensor_type_cb(VariableItem* item) {
 
     LightMeterConfig* config = app->config;
     config->sensor_type = index;
+
+    update_item_addr(app);
+
     lightmeter_app_set_config(app, config);
 }
 
@@ -195,6 +270,36 @@ void lightmeter_scene_config_on_enter(void* context) {
     variable_item_set_current_value_index(item, config->sensor_type);
     variable_item_set_current_value_text(item, sensor_type[config->sensor_type]);
 
+    item = variable_item_list_add(
+        var_item_list,
+        "Resolution",
+        COUNT_OF(measurement_resolution),
+        measurement_resolution_cb,
+        app);
+    variable_item_set_current_value_index(item, config->measurement_resolution);
+    variable_item_set_current_value_text(
+        item, measurement_resolution[config->measurement_resolution]);
+
+    switch(config->sensor_type) {
+    case SENSOR_BH1750:
+        item = variable_item_list_add(
+            var_item_list, "I2C address", COUNT_OF(device_addr_bh1750), device_addr_cb, app);
+        variable_item_set_current_value_index(item, config->device_addr);
+        variable_item_set_current_value_text(item, device_addr_bh1750[config->device_addr]);
+        break;
+    case SENSOR_MAX44009:
+        item = variable_item_list_add(
+            var_item_list, "I2C address", COUNT_OF(device_addr_max44009), device_addr_cb, app);
+        variable_item_set_current_value_index(item, config->device_addr);
+        variable_item_set_current_value_text(item, device_addr_max44009[config->device_addr]);
+        break;
+    default:
+        FURI_LOG_E(TAG, "Invalid sensor type %ld", config->sensor_type);
+        return;
+    }
+    app->var_item_addr = item;
+    update_item_addr(app);
+
     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);
 
@@ -235,4 +340,5 @@ void lightmeter_scene_config_on_exit(void* context) {
     main_view_set_nd(app->main_view, app->config->nd);
     main_view_set_dome(app->main_view, app->config->dome);
     main_view_set_lux_only(app->main_view, app->config->lux_only);
+    main_view_set_measurement_resolution(app->main_view, app->config->measurement_resolution);
 }

+ 7 - 1
gui/scenes/lightmeter_scene_help.c

@@ -7,7 +7,7 @@ void lightmeter_scene_help_on_enter(void* context) {
     temp_str = furi_string_alloc();
     furi_string_printf(
         temp_str,
-        "App works with BH1750 and MAX44409 ambient light sensor connected via I2C interface\n\n");
+        "App works with BH1750/MAX44009\nambient light sensor\nconnected via I2C interface\n\n");
     furi_string_cat(temp_str, "\e#Pinout:\r\n");
     furi_string_cat(
         temp_str,
@@ -15,6 +15,12 @@ void lightmeter_scene_help_on_enter(void* context) {
         "    GND: GND\r\n"
         "    SDA: 15 [C1]\r\n"
         "    SCL: 16 [C0]\r\n");
+    furi_string_cat(temp_str, "\r\n\e#Resolutions:\r\n");
+    furi_string_cat(
+        temp_str,
+        "Low: 4.0lx (16ms, 0-54k)\r\n"
+        "High: 1.0lx (120ms, 0-54k)\r\n"
+        "High2: 0.5lx (120ms, 0-27k)\r\n");
 
     widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
     furi_string_free(temp_str);

+ 18 - 2
gui/scenes/lightmeter_scene_main.c

@@ -6,11 +6,24 @@ static void lightmeter_scene_main_on_left(void* context) {
     view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventConfig);
 }
 
+static void lightmeter_scene_main_on_right(void* context) {
+    LightMeterApp* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventReset);
+}
+
 void lightmeter_scene_main_on_enter(void* context) {
     LightMeterApp* app = context;
 
-    lightmeter_app_i2c_init_sensor(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);
+    main_view_set_lux_only(app->main_view, app->config->lux_only);
+    main_view_set_measurement_resolution(app->main_view, app->config->measurement_resolution);
+
     lightmeter_main_view_set_left_callback(app->main_view, lightmeter_scene_main_on_left, app);
+    lightmeter_main_view_set_right_callback(app->main_view, lightmeter_scene_main_on_right, app);
     view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewMainView);
 }
 
@@ -24,6 +37,9 @@ bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) {
         if(event.event == LightMeterAppCustomEventConfig) {
             scene_manager_next_scene(app->scene_manager, LightMeterAppSceneConfig);
             response = true;
+        } else if(event.event == LightMeterAppCustomEventReset) {
+            lightmeter_app_reset_callback(app);
+            response = true;
         }
         break;
 
@@ -40,5 +56,5 @@ bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) {
 }
 
 void lightmeter_scene_main_on_exit(void* context) {
-    lightmeter_app_i2c_deinit_sensor(context);
+    UNUSED(context);
 }

+ 81 - 3
gui/views/main_view.c

@@ -1,4 +1,5 @@
 #include "main_view.h"
+#include <math.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <gui/elements.h>
@@ -72,6 +73,7 @@ const float speed_numbers[] = {
 struct MainView {
     View* view;
     LightMeterMainViewButtonCallback cb_left;
+    LightMeterMainViewButtonCallback cb_right;
     void* cb_context;
 };
 
@@ -90,6 +92,21 @@ void lightmeter_main_view_set_left_callback(
         true);
 }
 
+void lightmeter_main_view_set_right_callback(
+    MainView* lightmeter_main_view,
+    LightMeterMainViewButtonCallback callback,
+    void* context) {
+    with_view_model(
+        lightmeter_main_view->view,
+        MainViewModel * model,
+        {
+            UNUSED(model);
+            lightmeter_main_view->cb_right = callback;
+            lightmeter_main_view->cb_context = context;
+        },
+        true);
+}
+
 static void main_view_draw_callback(Canvas* canvas, void* context) {
     furi_assert(context);
     MainViewModel* model = context;
@@ -125,6 +142,7 @@ static void main_view_draw_callback(Canvas* canvas, void* context) {
         // draw mode indicator
         draw_mode_indicator(canvas, model);
     } else {
+        elements_button_right(canvas, "Reset");
         draw_lux_only_mode(canvas, model);
     }
 }
@@ -190,6 +208,11 @@ static bool main_view_input_callback(InputEvent* event, void* context) {
             main_view->cb_left(main_view->cb_context);
         }
         consumed = true;
+    } else if(event->type == InputTypeShort && event->key == InputKeyRight) {
+        if(main_view->cb_right) {
+            main_view->cb_right(main_view->cb_context);
+        }
+        consumed = true;
     } else if(event->type == InputTypeShort && event->key == InputKeyBack) {
     } else {
         main_view_process(main_view, event);
@@ -224,7 +247,22 @@ View* main_view_get_view(MainView* main_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);
+        main_view->view,
+        MainViewModel * model,
+        {
+            model->lux = val;
+            model->peakLux = fmax(model->peakLux, val);
+
+            model->luxHistogram[model->luxHistogramIndex++] = val;
+            model->luxHistogramIndex %= LUX_HISTORGRAM_LENGTH;
+        },
+        true);
+}
+
+void main_view_reset_lux(MainView* main_view) {
+    furi_assert(main_view);
+    with_view_model(
+        main_view->view, MainViewModel * model, { model->peakLux = 0; }, true);
 }
 
 void main_view_set_EV(MainView* main_view, float val) {
@@ -275,6 +313,27 @@ void main_view_set_lux_only(MainView* main_view, bool lux_only) {
         main_view->view, MainViewModel * model, { model->lux_only = lux_only; }, true);
 }
 
+void main_view_set_measurement_resolution(MainView* main_view, int measurement_resolution) {
+    furi_assert(main_view);
+    with_view_model(
+        main_view->view,
+        MainViewModel * model,
+        { model->measurement_resolution = measurement_resolution; },
+        true);
+}
+
+void main_view_set_device_addr(MainView* main_view, int device_addr) {
+    furi_assert(main_view);
+    with_view_model(
+        main_view->view, MainViewModel * model, { model->device_addr = device_addr; }, true);
+}
+
+void main_view_set_sensor_type(MainView* main_view, int sensor_type) {
+    furi_assert(main_view);
+    with_view_model(
+        main_view->view, MainViewModel * model, { model->sensor_type = sensor_type; }, true);
+}
+
 bool main_view_get_dome(MainView* main_view) {
     furi_assert(main_view);
     bool val = false;
@@ -462,9 +521,28 @@ void draw_lux_only_mode(Canvas* canvas, MainViewModel* context) {
 
         canvas_set_font(canvas, FontBigNumbers);
         snprintf(str, sizeof(str), "%.0f", (double)model->lux);
-        canvas_draw_str_aligned(canvas, 80, 32, AlignRight, AlignCenter, str);
+        canvas_draw_str_aligned(canvas, 80, 22, AlignRight, AlignCenter, str);
 
         canvas_set_font(canvas, FontSecondary);
-        canvas_draw_str_aligned(canvas, 85, 39, AlignLeft, AlignBottom, "Lux");
+        canvas_draw_str_aligned(canvas, 85, 29, AlignLeft, AlignBottom, "Lux now");
+
+        canvas_set_font(canvas, FontPrimary);
+        snprintf(str, sizeof(str), "%.0f", (double)model->peakLux);
+        canvas_draw_str_aligned(canvas, 80, 39, AlignRight, AlignCenter, str);
+
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 85, 43, AlignLeft, AlignBottom, "Lux peak");
+
+        for(int i = 0; i < LUX_HISTORGRAM_LENGTH; i++) {
+            float lux =
+                model->luxHistogram[(i + model->luxHistogramIndex) % LUX_HISTORGRAM_LENGTH];
+            int barHeight = log10(lux) / log10(LUX_HISTORGRAM_LOGBASE);
+            canvas_draw_line(
+                canvas,
+                LUX_HISTORGRAM_LEFT + i,
+                LUX_HISTORGRAM_BOTTOM,
+                LUX_HISTORGRAM_LEFT + i,
+                LUX_HISTORGRAM_BOTTOM - barHeight);
+        }
     }
 }

+ 32 - 0
gui/views/main_view.h

@@ -4,6 +4,18 @@
 #include "lightmeter_icons.h"
 #include "../../lightmeter_config.h"
 
+/* log base 1.4 and 12 pixels cut off
+   makes it show values approx 65-65k
+   with reasonable resolution in 1-10k range
+   on 20px of screen height  */
+#define LUX_HISTORGRAM_LOGBASE 1.4
+#define LUX_HISTORGRAM_BOTTOM 64 + 12
+
+/* 40 pixels between 45th and 85th
+   between left and right button labels */
+#define LUX_HISTORGRAM_LEFT 45
+#define LUX_HISTORGRAM_LENGTH 40
+
 typedef struct MainView MainView;
 
 typedef enum {
@@ -17,6 +29,7 @@ typedef struct {
     uint8_t recv[2];
     MainViewMode current_mode;
     float lux;
+    float peakLux;
     float EV;
     float aperture_val;
     float speed_val;
@@ -28,6 +41,12 @@ typedef struct {
     int speed;
     bool dome;
     bool lux_only;
+    int measurement_resolution;
+    int device_addr;
+    int sensor_type;
+
+    float luxHistogram[LUX_HISTORGRAM_LENGTH];
+    int luxHistogramIndex;
 } MainViewModel;
 
 typedef void (*LightMeterMainViewButtonCallback)(void* context);
@@ -37,6 +56,11 @@ void lightmeter_main_view_set_left_callback(
     LightMeterMainViewButtonCallback callback,
     void* context);
 
+void lightmeter_main_view_set_right_callback(
+    MainView* lightmeter_main_view,
+    LightMeterMainViewButtonCallback callback,
+    void* context);
+
 MainView* main_view_alloc();
 
 void main_view_free(MainView* main_view);
@@ -45,6 +69,8 @@ View* main_view_get_view(MainView* main_view);
 
 void main_view_set_lux(MainView* main_view, float val);
 
+void main_view_reset_lux(MainView* main_view);
+
 void main_view_set_EV(MainView* main_view_, float val);
 
 void main_view_set_response(MainView* main_view_, bool val);
@@ -61,6 +87,12 @@ void main_view_set_dome(MainView* main_view, bool val);
 
 void main_view_set_lux_only(MainView* main_view, bool val);
 
+void main_view_set_measurement_resolution(MainView* main_view, int val);
+
+void main_view_set_device_addr(MainView* main_view, int addr);
+
+void main_view_set_sensor_type(MainView* main_view, int sensor_type);
+
 bool main_view_get_dome(MainView* main_view);
 
 void draw_top_row(Canvas* canvas, MainViewModel* context);

+ 12 - 4
lib/MAX44009/MAX44009.c

@@ -2,13 +2,20 @@
 #include <math.h>
 #include <furi.h>
 
+uint8_t max44009_addr = MAX44009_ADDR;
+
 void max44009_init() {
     furi_hal_i2c_acquire(I2C_BUS);
-    furi_hal_i2c_write_reg_8(I2C_BUS, MAX44009_ADDR,
-        MAX44009_REG_CONFIG, MAX44009_REG_CONFIG_CONT_MODE, I2C_TIMEOUT);
+    furi_hal_i2c_write_reg_8(
+        I2C_BUS, max44009_addr, MAX44009_REG_CONFIG, MAX44009_REG_CONFIG_CONT_MODE, I2C_TIMEOUT);
     furi_hal_i2c_release(I2C_BUS);
 }
 
+void max44009_init_with_addr(uint8_t addr) {
+    max44009_addr = (addr << 1);
+    return max44009_init();
+}
+
 int max44009_read_light(float* result) {
     uint8_t data_one = 0;
     uint8_t exp, mantissa;
@@ -18,10 +25,11 @@ int max44009_read_light(float* result) {
     furi_hal_i2c_read_reg_8(I2C_BUS, MAX44009_ADDR, MAX44009_REG_LUX_HI, &data_one, I2C_TIMEOUT);
     exp = (data_one & MAX44009_REG_LUX_HI_EXP_MASK) >> 4;
     mantissa = (data_one & MAX44009_REG_LUX_HI_MANT_HI_MASK) << 4;
-    status = furi_hal_i2c_read_reg_8(I2C_BUS, MAX44009_ADDR, MAX44009_REG_LUX_LO, &data_one, I2C_TIMEOUT);
+    status = furi_hal_i2c_read_reg_8(
+        I2C_BUS, MAX44009_ADDR, MAX44009_REG_LUX_LO, &data_one, I2C_TIMEOUT);
     mantissa |= (data_one & MAX44009_REG_LUX_LO_MANT_LO_MASK);
     furi_hal_i2c_release(I2C_BUS);
     *result = (float)pow(2, exp) * mantissa * 0.045;
     FURI_LOG_D("MAX44009", "exp %d, mant %d, lux %f", exp, mantissa, (double)*result);
     return status;
-}
+}

+ 1 - 0
lib/MAX44009/MAX44009.h

@@ -23,4 +23,5 @@
 #define MAX44009_REG_INT_TIME 0x07
 
 void max44009_init();
+void max44009_init_with_addr(uint8_t addr);
 int max44009_read_light(float* result);

+ 75 - 4
lightmeter.c

@@ -34,11 +34,35 @@ LightMeterApp* lightmeter_app_alloc(uint32_t first_scene) {
     app->config->aperture = DEFAULT_APERTURE;
     app->config->dome = DEFAULT_DOME;
     app->config->backlight = DEFAULT_BACKLIGHT;
+    app->config->measurement_resolution = HIGH_RES;
+    app->config->device_addr = ADDR_LOW;
+    app->config->lux_only = LUX_ONLY_OFF;
 
     // Records
     app->gui = furi_record_open(RECORD_GUI);
+    app->storage = furi_record_open(RECORD_STORAGE);
     app->notifications = furi_record_open(RECORD_NOTIFICATION);
 
+    app->cfg_path = furi_string_alloc();
+    furi_string_printf(app->cfg_path, "%s/%s", APP_PATH_DIR, APP_PATH_CFG);
+
+    FlipperFormat* cfg_fmt = flipper_format_file_alloc(app->storage);
+    if(flipper_format_file_open_existing(cfg_fmt, furi_string_get_cstr(app->cfg_path))) {
+        flipper_format_read_int32(cfg_fmt, "iso", &app->config->iso, 1);
+        flipper_format_read_int32(cfg_fmt, "aperture", &app->config->aperture, 1);
+        flipper_format_read_int32(cfg_fmt, "dome", &app->config->dome, 1);
+        flipper_format_read_int32(cfg_fmt, "backlight", &app->config->backlight, 1);
+        flipper_format_read_int32(
+            cfg_fmt, "measurement_resolution", &app->config->measurement_resolution, 1);
+        flipper_format_read_int32(cfg_fmt, "lux_only", &app->config->lux_only, 1);
+        flipper_format_read_int32(cfg_fmt, "device_addr", &app->config->device_addr, 1);
+        flipper_format_read_int32(cfg_fmt, "sensor_type", &app->config->sensor_type, 1);
+    }
+    flipper_format_free(cfg_fmt);
+
+    // Sensor
+    lightmeter_app_i2c_init_sensor(app);
+
     // View dispatcher
     app->view_dispatcher = view_dispatcher_alloc();
     app->scene_manager = scene_manager_alloc(&lightmeter_scene_handlers, app);
@@ -110,8 +134,11 @@ void lightmeter_app_free(LightMeterApp* app) {
             app->notifications,
             &sequence_display_backlight_enforce_auto); // set backlight back to auto
     }
+    furi_record_close(RECORD_STORAGE);
     furi_record_close(RECORD_NOTIFICATION);
 
+    bh1750_set_power_state(0);
+
     free(app->config);
     free(app);
 }
@@ -129,6 +156,24 @@ void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config)
     LightMeterApp* app = context;
 
     app->config = config;
+    storage_common_mkdir(app->storage, APP_PATH_DIR);
+
+    FlipperFormat* cfg_fmt = flipper_format_file_alloc(app->storage);
+    if(flipper_format_file_open_always(cfg_fmt, furi_string_get_cstr(app->cfg_path))) {
+        flipper_format_write_header_cstr(cfg_fmt, "lightmeter", 1);
+
+        flipper_format_write_int32(cfg_fmt, "iso", &(app->config->iso), 1);
+        flipper_format_write_int32(cfg_fmt, "nd", &(app->config->nd), 1);
+        flipper_format_write_int32(cfg_fmt, "aperture", &(app->config->aperture), 1);
+        flipper_format_write_int32(cfg_fmt, "dome", &(app->config->dome), 1);
+        flipper_format_write_int32(cfg_fmt, "backlight", &(app->config->backlight), 1);
+        flipper_format_write_int32(
+            cfg_fmt, "measurement_resolution", &(app->config->measurement_resolution), 1);
+        flipper_format_write_int32(cfg_fmt, "lux_only", &(app->config->lux_only), 1);
+        flipper_format_write_int32(cfg_fmt, "device_addr", &(app->config->device_addr), 1);
+        flipper_format_write_int32(cfg_fmt, "sensor_type", &(app->config->sensor_type), 1);
+    }
+    flipper_format_free(cfg_fmt);
 }
 
 void lightmeter_app_i2c_init_sensor(LightMeterApp* context) {
@@ -136,14 +181,34 @@ void lightmeter_app_i2c_init_sensor(LightMeterApp* context) {
     switch(app->config->sensor_type) {
     case SENSOR_BH1750:
         bh1750_set_power_state(1);
-        bh1750_init();
+        switch(app->config->device_addr) {
+        case ADDR_HIGH:
+            bh1750_init_with_addr(0x5C);
+            break;
+        case ADDR_LOW:
+            bh1750_init_with_addr(0x23);
+            break;
+        default:
+            bh1750_init_with_addr(0x23);
+            break;
+        }
         bh1750_set_mode(ONETIME_HIGH_RES_MODE);
         break;
     case SENSOR_MAX44009:
-        max44009_init();
+        switch(app->config->device_addr) {
+        case ADDR_HIGH:
+            max44009_init_with_addr(0x4B);
+            break;
+        case ADDR_LOW:
+            max44009_init_with_addr(0x4A);
+            break;
+        default:
+            max44009_init_with_addr(0x4A);
+            break;
+        }
         break;
     default:
-        FURI_LOG_E(TAG, "Invalid sensor type %d", app->config->sensor_type);
+        FURI_LOG_E(TAG, "Invalid sensor type %ld", app->config->sensor_type);
         return;
     }
 }
@@ -158,7 +223,7 @@ void lightmeter_app_i2c_deinit_sensor(LightMeterApp* context) {
         // nothing
         break;
     default:
-        FURI_LOG_E(TAG, "Invalid sensor type %d", app->config->sensor_type);
+        FURI_LOG_E(TAG, "Invalid sensor type %ld", app->config->sensor_type);
         return;
     }
 }
@@ -186,3 +251,9 @@ void lightmeter_app_i2c_callback(LightMeterApp* context) {
     main_view_set_EV(app->main_view, EV);
     main_view_set_response(app->main_view, response);
 }
+
+void lightmeter_app_reset_callback(LightMeterApp* context) {
+    LightMeterApp* app = context;
+
+    main_view_reset_lux(app->main_view);
+}

+ 24 - 7
lightmeter.h

@@ -3,6 +3,9 @@
 #include <furi.h>
 #include <furi_hal.h>
 
+#include <stream/stream.h>
+#include <flipper_format/flipper_format_i.h>
+
 #include <gui/gui.h>
 #include <gui/view.h>
 #include <gui/view_dispatcher.h>
@@ -20,14 +23,19 @@
 #include <BH1750.h>
 #include <MAX44009.h>
 
+#define APP_PATH_DIR STORAGE_APP_DATA_PATH_PREFIX
+#define APP_PATH_CFG "config.txt"
+
 typedef struct {
-    int iso;
-    int nd;
-    int aperture;
-    int dome;
-    int backlight;
-    int lux_only;
-    int sensor_type;
+    int32_t iso;
+    int32_t nd;
+    int32_t aperture;
+    int32_t dome;
+    int32_t backlight;
+    int32_t lux_only;
+    int32_t sensor_type;
+    int32_t measurement_resolution;
+    int32_t device_addr;
 } LightMeterConfig;
 
 typedef struct {
@@ -36,9 +44,13 @@ typedef struct {
     ViewDispatcher* view_dispatcher;
     MainView* main_view;
     VariableItemList* var_item_list;
+    VariableItem* var_item_addr;
     LightMeterConfig* config;
     NotificationApp* notifications;
     Widget* widget;
+
+    Storage* storage;
+    FuriString* cfg_path;
 } LightMeterApp;
 
 typedef enum {
@@ -50,6 +62,7 @@ typedef enum {
 } LightMeterAppView;
 
 typedef enum {
+    LightMeterAppCustomEventReset,
     LightMeterAppCustomEventConfig,
     LightMeterAppCustomEventHelp,
     LightMeterAppCustomEventAbout,
@@ -58,5 +71,9 @@ typedef enum {
 void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config);
 
 void lightmeter_app_i2c_init_sensor(LightMeterApp* context);
+
 void lightmeter_app_i2c_deinit_sensor(LightMeterApp* context);
+
 void lightmeter_app_i2c_callback(LightMeterApp* context);
+
+void lightmeter_app_reset_callback(LightMeterApp* context);

+ 12 - 1
lightmeter_config.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#define LM_VERSION_APP "1.1"
+#define LM_VERSION_APP "1.2"
 #define LM_DEVELOPED "Oleksii Kutuzov"
 #define LM_GITHUB "https://github.com/oleksiikutuzov/flipperzero-lightmeter"
 
@@ -105,6 +105,17 @@ typedef enum {
     LUX_ONLY_ON,
 } LightMeterLuxOnlyMode;
 
+typedef enum {
+    LOW_RES,
+    HIGH_RES,
+    HIGH_RES2,
+} LightMeterMeterMode;
+
+typedef enum {
+    ADDR_LOW,
+    ADDR_HIGH,
+} LightMeterMeterAddr;
+
 typedef enum {
     SENSOR_BH1750,
     SENSOR_MAX44009,