diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..fb51c54c --- /dev/null +++ b/.clang-format @@ -0,0 +1,139 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 2000 +PointerAlignment: Right +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 2 +UseTab: Never +... diff --git a/.github/workflows/validate_clang_format.yml b/.github/workflows/validate_clang_format.yml index 6f8b40a2..b95e34a8 100644 --- a/.github/workflows/validate_clang_format.yml +++ b/.github/workflows/validate_clang_format.yml @@ -8,17 +8,33 @@ on: - '**/*.h' - '**/*.c' - '**/*.cpp' + - '.clang-format' workflow_dispatch: permissions: - contents: read + contents: write # Required for auto-commit jobs: clang-format-checking: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: RafikFarhad/clang-format-github-action@v6 with: - sources: "**/*.h,**/*.c,**/*.cpp" + ref: ${{ github.head_ref || github.ref_name }} # PR head branch, otherwise selected dispatch ref + + - name: Install clang-format + run: sudo apt-get install -y clang-format + + - name: Apply clang-format + run: | + find ./components/nspanel_easy ./.test/unit \ + \( -name "*.h" -o -name "*.c" -o -name "*.cpp" \) \ + -print0 | xargs -0 -r clang-format --style=file -i + + - name: Commit clang-format fixes + if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: "style: apply clang-format" + commit_options: "--no-verify" ... diff --git a/.test/unit/test_addon_climate.cpp b/.test/unit/test_addon_climate.cpp index f56096bf..14385a4d 100644 --- a/.test/unit/test_addon_climate.cpp +++ b/.test/unit/test_addon_climate.cpp @@ -4,6 +4,8 @@ */ #include +#include +#include #include // Define the macro before including the header @@ -11,68 +13,64 @@ // Mock icons.h structures namespace nspanel_easy { - namespace Icons { - constexpr uint16_t NONE = 0; - constexpr uint16_t AUTORENEW = 1; - constexpr uint16_t SNOWFLAKE = 2; - constexpr uint16_t FIRE = 3; - constexpr uint16_t FAN = 4; - constexpr uint16_t WATER_PERCENT = 5; - constexpr uint16_t CALENDAR_SYNC = 6; - constexpr uint16_t THERMOMETER = 7; - } - - namespace Colors { - constexpr uint32_t BLACK = 0x000000; - constexpr uint32_t GRAY = 0x808080; - constexpr uint32_t BLUE = 0x0000FF; - constexpr uint32_t DEEP_ORANGE = 0xFF5722; - constexpr uint32_t ORANGE = 0xFFA500; - constexpr uint32_t CYAN = 0x00FFFF; - } - - struct IconData { - uint16_t icon; - uint32_t color; - }; -} +namespace Icons { +constexpr uint16_t NONE = 0; +constexpr uint16_t AUTORENEW = 1; +constexpr uint16_t SNOWFLAKE = 2; +constexpr uint16_t FIRE = 3; +constexpr uint16_t FAN = 4; +constexpr uint16_t WATER_PERCENT = 5; +constexpr uint16_t CALENDAR_SYNC = 6; +constexpr uint16_t THERMOMETER = 7; +} // namespace Icons + +namespace Colors { +constexpr uint32_t BLACK = 0x000000; +constexpr uint32_t GRAY = 0x808080; +constexpr uint32_t BLUE = 0x0000FF; +constexpr uint32_t DEEP_ORANGE = 0xFF5722; +constexpr uint32_t ORANGE = 0xFFA500; +constexpr uint32_t CYAN = 0x00FFFF; +} // namespace Colors + +struct IconData { + uint16_t icon; + uint32_t color; +}; +} // namespace nspanel_easy #include "../components/nspanel_easy/addon_climate.h" namespace nspanel_easy { class AddonClimateTest : public ::testing::Test { -protected: - void SetUp() override { - // Reset global variables before each test - addon_climate_friendly_name = "Thermostat"; - is_addon_climate_visible = false; - } + protected: + void SetUp() override { + // Reset global variables before each test + addon_climate_friendly_name = "Thermostat"; + is_addon_climate_visible = false; + } }; // ============================================================================= // Global Variable Tests // ============================================================================= -TEST_F(AddonClimateTest, FriendlyNameDefaultValue) { - EXPECT_EQ(addon_climate_friendly_name, "Thermostat"); -} +TEST_F(AddonClimateTest, FriendlyNameDefaultValue) { EXPECT_EQ(addon_climate_friendly_name, "Thermostat"); } TEST_F(AddonClimateTest, FriendlyNameCanBeModified) { - addon_climate_friendly_name = "Living Room Climate"; - EXPECT_EQ(addon_climate_friendly_name, "Living Room Climate"); + addon_climate_friendly_name = "Living Room Climate"; + EXPECT_EQ(addon_climate_friendly_name, "Living Room Climate"); } -TEST_F(AddonClimateTest, VisibilityDefaultValue) { - EXPECT_FALSE(is_addon_climate_visible); -} +TEST_F(AddonClimateTest, VisibilityDefaultValue) { EXPECT_FALSE(is_addon_climate_visible); } TEST_F(AddonClimateTest, VisibilityCanBeToggled) { - is_addon_climate_visible = true; - EXPECT_TRUE(is_addon_climate_visible); + is_addon_climate_visible = true; + EXPECT_TRUE(is_addon_climate_visible); - is_addon_climate_visible = false; - EXPECT_FALSE(is_addon_climate_visible); + is_addon_climate_visible = false; + EXPECT_FALSE(is_addon_climate_visible); } // ============================================================================= @@ -80,30 +78,24 @@ TEST_F(AddonClimateTest, VisibilityCanBeToggled) { // ============================================================================= TEST_F(AddonClimateTest, ClimateActionEnumValues) { - EXPECT_EQ(CLIMATE_ACTION_OFF, 0); - EXPECT_EQ(CLIMATE_ACTION_COOLING, 2); - EXPECT_EQ(CLIMATE_ACTION_HEATING, 3); - EXPECT_EQ(CLIMATE_ACTION_IDLE, 4); - EXPECT_EQ(CLIMATE_ACTION_DRYING, 5); - EXPECT_EQ(CLIMATE_ACTION_FAN, 6); + EXPECT_EQ(CLIMATE_ACTION_OFF, 0); + EXPECT_EQ(CLIMATE_ACTION_COOLING, 2); + EXPECT_EQ(CLIMATE_ACTION_HEATING, 3); + EXPECT_EQ(CLIMATE_ACTION_IDLE, 4); + EXPECT_EQ(CLIMATE_ACTION_DRYING, 5); + EXPECT_EQ(CLIMATE_ACTION_FAN, 6); } TEST_F(AddonClimateTest, ClimateActionEnumSize) { - // Verify enum is uint8_t - EXPECT_EQ(sizeof(ClimateAction), sizeof(uint8_t)); + // Verify enum is uint8_t + EXPECT_EQ(sizeof(ClimateAction), sizeof(uint8_t)); } TEST_F(AddonClimateTest, ClimateActionValuesAreUnique) { - // Ensure no duplicate values - std::set values = { - CLIMATE_ACTION_OFF, - CLIMATE_ACTION_COOLING, - CLIMATE_ACTION_HEATING, - CLIMATE_ACTION_IDLE, - CLIMATE_ACTION_DRYING, - CLIMATE_ACTION_FAN - }; - EXPECT_EQ(values.size(), 6); + // Ensure no duplicate values + std::set values = {CLIMATE_ACTION_OFF, CLIMATE_ACTION_COOLING, CLIMATE_ACTION_HEATING, + CLIMATE_ACTION_IDLE, CLIMATE_ACTION_DRYING, CLIMATE_ACTION_FAN}; + EXPECT_EQ(values.size(), 6); } // ============================================================================= @@ -111,31 +103,24 @@ TEST_F(AddonClimateTest, ClimateActionValuesAreUnique) { // ============================================================================= TEST_F(AddonClimateTest, ClimateModeEnumValues) { - EXPECT_EQ(CLIMATE_MODE_OFF, 0); - EXPECT_EQ(CLIMATE_MODE_HEAT_COOL, 1); - EXPECT_EQ(CLIMATE_MODE_COOL, 2); - EXPECT_EQ(CLIMATE_MODE_HEAT, 3); - EXPECT_EQ(CLIMATE_MODE_FAN_ONLY, 4); - EXPECT_EQ(CLIMATE_MODE_DRY, 5); - EXPECT_EQ(CLIMATE_MODE_AUTO, 6); + EXPECT_EQ(CLIMATE_MODE_OFF, 0); + EXPECT_EQ(CLIMATE_MODE_HEAT_COOL, 1); + EXPECT_EQ(CLIMATE_MODE_COOL, 2); + EXPECT_EQ(CLIMATE_MODE_HEAT, 3); + EXPECT_EQ(CLIMATE_MODE_FAN_ONLY, 4); + EXPECT_EQ(CLIMATE_MODE_DRY, 5); + EXPECT_EQ(CLIMATE_MODE_AUTO, 6); } TEST_F(AddonClimateTest, ClimateModeEnumSize) { - // Verify enum is uint8_t - EXPECT_EQ(sizeof(ClimateMode), sizeof(uint8_t)); + // Verify enum is uint8_t + EXPECT_EQ(sizeof(ClimateMode), sizeof(uint8_t)); } TEST_F(AddonClimateTest, ClimateModeValuesAreUnique) { - std::set values = { - CLIMATE_MODE_OFF, - CLIMATE_MODE_HEAT_COOL, - CLIMATE_MODE_COOL, - CLIMATE_MODE_HEAT, - CLIMATE_MODE_FAN_ONLY, - CLIMATE_MODE_DRY, - CLIMATE_MODE_AUTO - }; - EXPECT_EQ(values.size(), 7); + std::set values = {CLIMATE_MODE_OFF, CLIMATE_MODE_HEAT_COOL, CLIMATE_MODE_COOL, CLIMATE_MODE_HEAT, + CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_AUTO}; + EXPECT_EQ(values.size(), 7); } // ============================================================================= @@ -143,103 +128,100 @@ TEST_F(AddonClimateTest, ClimateModeValuesAreUnique) { // ============================================================================= TEST_F(AddonClimateTest, ClimateOffModeIconsTableSize) { - EXPECT_EQ(sizeof(climate_off_mode_icons) / sizeof(IconData), 7); + EXPECT_EQ(sizeof(climate_off_mode_icons) / sizeof(IconData), 7); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeOff) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_OFF]; - EXPECT_EQ(icon_data.icon, Icons::NONE); - EXPECT_EQ(icon_data.color, Colors::BLACK); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_OFF]; + EXPECT_EQ(icon_data.icon, Icons::NONE); + EXPECT_EQ(icon_data.color, Colors::BLACK); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeHeatCool) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_HEAT_COOL]; - EXPECT_EQ(icon_data.icon, Icons::AUTORENEW); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_HEAT_COOL]; + EXPECT_EQ(icon_data.icon, Icons::AUTORENEW); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeCool) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_COOL]; - EXPECT_EQ(icon_data.icon, Icons::SNOWFLAKE); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_COOL]; + EXPECT_EQ(icon_data.icon, Icons::SNOWFLAKE); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeHeat) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_HEAT]; - EXPECT_EQ(icon_data.icon, Icons::FIRE); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_HEAT]; + EXPECT_EQ(icon_data.icon, Icons::FIRE); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeFanOnly) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_FAN_ONLY]; - EXPECT_EQ(icon_data.icon, Icons::FAN); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_FAN_ONLY]; + EXPECT_EQ(icon_data.icon, Icons::FAN); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeDry) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_DRY]; - EXPECT_EQ(icon_data.icon, Icons::WATER_PERCENT); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_DRY]; + EXPECT_EQ(icon_data.icon, Icons::WATER_PERCENT); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_ModeAuto) { - const auto& icon_data = climate_off_mode_icons[CLIMATE_MODE_AUTO]; - EXPECT_EQ(icon_data.icon, Icons::CALENDAR_SYNC); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_off_mode_icons[CLIMATE_MODE_AUTO]; + EXPECT_EQ(icon_data.icon, Icons::CALENDAR_SYNC); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateOffModeIcons_AllGrayExceptModeOff) { - // All icons except MODE_OFF should be gray when device is off - for (size_t i = 1; i < 7; ++i) { - EXPECT_EQ(climate_off_mode_icons[i].color, Colors::GRAY) - << "Icon at index " << i << " should be GRAY when off"; - } + // All icons except MODE_OFF should be gray when device is off + for (size_t i = 1; i < 7; ++i) { + EXPECT_EQ(climate_off_mode_icons[i].color, Colors::GRAY) << "Icon at index " << i << " should be GRAY when off"; + } } // ============================================================================= // Climate Action Icon Lookup Table Tests // ============================================================================= -TEST_F(AddonClimateTest, ClimateActionIconsTableSize) { - EXPECT_EQ(sizeof(climate_action_icons) / sizeof(IconData), 7); -} +TEST_F(AddonClimateTest, ClimateActionIconsTableSize) { EXPECT_EQ(sizeof(climate_action_icons) / sizeof(IconData), 7); } TEST_F(AddonClimateTest, ClimateActionIcons_Cooling) { - const auto& icon_data = climate_action_icons[CLIMATE_ACTION_COOLING]; - EXPECT_EQ(icon_data.icon, Icons::SNOWFLAKE); - EXPECT_EQ(icon_data.color, Colors::BLUE); + const auto &icon_data = climate_action_icons[CLIMATE_ACTION_COOLING]; + EXPECT_EQ(icon_data.icon, Icons::SNOWFLAKE); + EXPECT_EQ(icon_data.color, Colors::BLUE); } TEST_F(AddonClimateTest, ClimateActionIcons_Heating) { - const auto& icon_data = climate_action_icons[CLIMATE_ACTION_HEATING]; - EXPECT_EQ(icon_data.icon, Icons::FIRE); - EXPECT_EQ(icon_data.color, Colors::DEEP_ORANGE); + const auto &icon_data = climate_action_icons[CLIMATE_ACTION_HEATING]; + EXPECT_EQ(icon_data.icon, Icons::FIRE); + EXPECT_EQ(icon_data.color, Colors::DEEP_ORANGE); } TEST_F(AddonClimateTest, ClimateActionIcons_Idle) { - const auto& icon_data = climate_action_icons[CLIMATE_ACTION_IDLE]; - EXPECT_EQ(icon_data.icon, Icons::THERMOMETER); - EXPECT_EQ(icon_data.color, Colors::GRAY); + const auto &icon_data = climate_action_icons[CLIMATE_ACTION_IDLE]; + EXPECT_EQ(icon_data.icon, Icons::THERMOMETER); + EXPECT_EQ(icon_data.color, Colors::GRAY); } TEST_F(AddonClimateTest, ClimateActionIcons_Drying) { - const auto& icon_data = climate_action_icons[CLIMATE_ACTION_DRYING]; - EXPECT_EQ(icon_data.icon, Icons::WATER_PERCENT); - EXPECT_EQ(icon_data.color, Colors::ORANGE); + const auto &icon_data = climate_action_icons[CLIMATE_ACTION_DRYING]; + EXPECT_EQ(icon_data.icon, Icons::WATER_PERCENT); + EXPECT_EQ(icon_data.color, Colors::ORANGE); } TEST_F(AddonClimateTest, ClimateActionIcons_Fan) { - const auto& icon_data = climate_action_icons[CLIMATE_ACTION_FAN]; - EXPECT_EQ(icon_data.icon, Icons::FAN); - EXPECT_EQ(icon_data.color, Colors::CYAN); + const auto &icon_data = climate_action_icons[CLIMATE_ACTION_FAN]; + EXPECT_EQ(icon_data.icon, Icons::FAN); + EXPECT_EQ(icon_data.color, Colors::CYAN); } TEST_F(AddonClimateTest, ClimateActionIcons_UnusedIndices) { - // Indices 0 and 1 are unused (reserved) - EXPECT_EQ(climate_action_icons[0].icon, Icons::NONE); - EXPECT_EQ(climate_action_icons[0].color, Colors::BLACK); - EXPECT_EQ(climate_action_icons[1].icon, Icons::NONE); - EXPECT_EQ(climate_action_icons[1].color, Colors::BLACK); + // Indices 0 and 1 are unused (reserved) + EXPECT_EQ(climate_action_icons[0].icon, Icons::NONE); + EXPECT_EQ(climate_action_icons[0].color, Colors::BLACK); + EXPECT_EQ(climate_action_icons[1].icon, Icons::NONE); + EXPECT_EQ(climate_action_icons[1].color, Colors::BLACK); } // ============================================================================= @@ -247,27 +229,27 @@ TEST_F(AddonClimateTest, ClimateActionIcons_UnusedIndices) { // ============================================================================= TEST_F(AddonClimateTest, IconConsistency_SnowflakeUsedForCooling) { - // Snowflake should be used for both OFF/COOL mode and COOLING action - EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_COOL].icon, Icons::SNOWFLAKE); - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_COOLING].icon, Icons::SNOWFLAKE); + // Snowflake should be used for both OFF/COOL mode and COOLING action + EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_COOL].icon, Icons::SNOWFLAKE); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_COOLING].icon, Icons::SNOWFLAKE); } TEST_F(AddonClimateTest, IconConsistency_FireUsedForHeating) { - // Fire should be used for both OFF/HEAT mode and HEATING action - EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_HEAT].icon, Icons::FIRE); - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_HEATING].icon, Icons::FIRE); + // Fire should be used for both OFF/HEAT mode and HEATING action + EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_HEAT].icon, Icons::FIRE); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_HEATING].icon, Icons::FIRE); } TEST_F(AddonClimateTest, IconConsistency_FanUsedForFanMode) { - // Fan should be used for both OFF/FAN_ONLY mode and FAN action - EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_FAN_ONLY].icon, Icons::FAN); - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_FAN].icon, Icons::FAN); + // Fan should be used for both OFF/FAN_ONLY mode and FAN action + EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_FAN_ONLY].icon, Icons::FAN); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_FAN].icon, Icons::FAN); } TEST_F(AddonClimateTest, IconConsistency_WaterPercentUsedForDry) { - // Water percent should be used for both OFF/DRY mode and DRYING action - EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_DRY].icon, Icons::WATER_PERCENT); - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_DRYING].icon, Icons::WATER_PERCENT); + // Water percent should be used for both OFF/DRY mode and DRYING action + EXPECT_EQ(climate_off_mode_icons[CLIMATE_MODE_DRY].icon, Icons::WATER_PERCENT); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_DRYING].icon, Icons::WATER_PERCENT); } // ============================================================================= @@ -275,24 +257,24 @@ TEST_F(AddonClimateTest, IconConsistency_WaterPercentUsedForDry) { // ============================================================================= TEST_F(AddonClimateTest, ColorSemantics_CoolingIsBlue) { - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_COOLING].color, Colors::BLUE); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_COOLING].color, Colors::BLUE); } TEST_F(AddonClimateTest, ColorSemantics_HeatingIsDeepOrange) { - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_HEATING].color, Colors::DEEP_ORANGE); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_HEATING].color, Colors::DEEP_ORANGE); } TEST_F(AddonClimateTest, ColorSemantics_IdleIsGray) { - EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_IDLE].color, Colors::GRAY); + EXPECT_EQ(climate_action_icons[CLIMATE_ACTION_IDLE].color, Colors::GRAY); } TEST_F(AddonClimateTest, ColorSemantics_OffModesAreGrayOrBlack) { - // All off modes should use GRAY or BLACK to indicate inactive state - for (size_t i = 0; i < 7; ++i) { - uint32_t color = climate_off_mode_icons[i].color; - EXPECT_TRUE(color == Colors::GRAY || color == Colors::BLACK) - << "Off mode at index " << i << " should be GRAY or BLACK"; - } + // All off modes should use GRAY or BLACK to indicate inactive state + for (size_t i = 0; i < 7; ++i) { + uint32_t color = climate_off_mode_icons[i].color; + EXPECT_TRUE(color == Colors::GRAY || color == Colors::BLACK) + << "Off mode at index " << i << " should be GRAY or BLACK"; + } } // ============================================================================= @@ -300,39 +282,39 @@ TEST_F(AddonClimateTest, ColorSemantics_OffModesAreGrayOrBlack) { // ============================================================================= TEST_F(AddonClimateTest, LookupTable_ValidIndicesAccess) { - // Test that all valid mode indices can be accessed - for (int i = 0; i < 7; ++i) { - EXPECT_NO_THROW({ - auto icon = climate_off_mode_icons[i]; - (void)icon; // Suppress unused variable warning - }); - } + // Test that all valid mode indices can be accessed + for (int i = 0; i < 7; ++i) { + EXPECT_NO_THROW({ + auto icon = climate_off_mode_icons[i]; + (void) icon; // Suppress unused variable warning + }); + } } TEST_F(AddonClimateTest, LookupTable_ValidActionIndicesAccess) { - // Test that all valid action indices can be accessed - for (int i = 0; i < 7; ++i) { - EXPECT_NO_THROW({ - auto icon = climate_action_icons[i]; - (void)icon; // Suppress unused variable warning - }); - } + // Test that all valid action indices can be accessed + for (int i = 0; i < 7; ++i) { + EXPECT_NO_THROW({ + auto icon = climate_action_icons[i]; + (void) icon; // Suppress unused variable warning + }); + } } TEST_F(AddonClimateTest, StringOperations_FriendlyNameEmpty) { - addon_climate_friendly_name = ""; - EXPECT_TRUE(addon_climate_friendly_name.empty()); + addon_climate_friendly_name = ""; + EXPECT_TRUE(addon_climate_friendly_name.empty()); } TEST_F(AddonClimateTest, StringOperations_FriendlyNameLong) { - std::string long_name(1000, 'A'); - addon_climate_friendly_name = long_name; - EXPECT_EQ(addon_climate_friendly_name.length(), 1000); + std::string long_name(1000, 'A'); + addon_climate_friendly_name = long_name; + EXPECT_EQ(addon_climate_friendly_name.length(), 1000); } TEST_F(AddonClimateTest, StringOperations_FriendlyNameSpecialCharacters) { - addon_climate_friendly_name = "Thërmöstàt 123 !@#"; - EXPECT_EQ(addon_climate_friendly_name, "Thërmöstàt 123 !@#"); + addon_climate_friendly_name = "Thërmöstàt 123 !@#"; + EXPECT_EQ(addon_climate_friendly_name, "Thërmöstàt 123 !@#"); } // ============================================================================= @@ -340,26 +322,26 @@ TEST_F(AddonClimateTest, StringOperations_FriendlyNameSpecialCharacters) { // ============================================================================= TEST_F(AddonClimateTest, Regression_NoIconDuplicatesInOffModes) { - // Verify each mode has a unique icon (except MODE_OFF which uses NONE) - std::set icons; - for (size_t i = 1; i < 7; ++i) { // Skip index 0 (MODE_OFF) - icons.insert(climate_off_mode_icons[i].icon); - } - EXPECT_EQ(icons.size(), 6) << "Each mode should have a unique icon"; + // Verify each mode has a unique icon (except MODE_OFF which uses NONE) + std::set icons; + for (size_t i = 1; i < 7; ++i) { // Skip index 0 (MODE_OFF) + icons.insert(climate_off_mode_icons[i].icon); + } + EXPECT_EQ(icons.size(), 6) << "Each mode should have a unique icon"; } TEST_F(AddonClimateTest, Regression_NoIconDuplicatesInActions) { - // Verify each action has a unique icon (except reserved indices) - std::set icons; - for (size_t i = 2; i < 7; ++i) { // Skip indices 0-1 (reserved) - icons.insert(climate_action_icons[i].icon); - } - EXPECT_EQ(icons.size(), 5) << "Each action should have a unique icon"; + // Verify each action has a unique icon (except reserved indices) + std::set icons; + for (size_t i = 2; i < 7; ++i) { // Skip indices 0-1 (reserved) + icons.insert(climate_action_icons[i].icon); + } + EXPECT_EQ(icons.size(), 5) << "Each action should have a unique icon"; } -} // namespace nspanel_easy +} // namespace nspanel_easy int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } \ No newline at end of file diff --git a/.test/unit/test_addon_upload_tft.cpp b/.test/unit/test_addon_upload_tft.cpp index 135ddbd9..6aa6dabf 100644 --- a/.test/unit/test_addon_upload_tft.cpp +++ b/.test/unit/test_addon_upload_tft.cpp @@ -14,102 +14,96 @@ namespace nspanel_easy { class AddonUploadTftTest : public ::testing::Test { -protected: - void SetUp() override { - // Reset global variables before each test - tft_upload_attempt = 0; - tft_upload_result = false; - } - - void TearDown() override { - // Clean up after each test - tft_upload_attempt = 0; - tft_upload_result = false; - } + protected: + void SetUp() override { + // Reset global variables before each test + tft_upload_attempt = 0; + tft_upload_result = false; + } + + void TearDown() override { + // Clean up after each test + tft_upload_attempt = 0; + tft_upload_result = false; + } }; // ============================================================================= // Global Variable Tests // ============================================================================= -TEST_F(AddonUploadTftTest, UploadAttemptDefaultValue) { - EXPECT_EQ(tft_upload_attempt, 0); -} +TEST_F(AddonUploadTftTest, UploadAttemptDefaultValue) { EXPECT_EQ(tft_upload_attempt, 0); } -TEST_F(AddonUploadTftTest, UploadResultDefaultValue) { - EXPECT_FALSE(tft_upload_result); -} +TEST_F(AddonUploadTftTest, UploadResultDefaultValue) { EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, UploadAttemptIsUint8) { - // Verify that tft_upload_attempt is uint8_t (can hold 0-255) - EXPECT_EQ(sizeof(tft_upload_attempt), sizeof(uint8_t)); + // Verify that tft_upload_attempt is uint8_t (can hold 0-255) + EXPECT_EQ(sizeof(tft_upload_attempt), sizeof(uint8_t)); } -TEST_F(AddonUploadTftTest, UploadResultIsBool) { - EXPECT_EQ(sizeof(tft_upload_result), sizeof(bool)); -} +TEST_F(AddonUploadTftTest, UploadResultIsBool) { EXPECT_EQ(sizeof(tft_upload_result), sizeof(bool)); } // ============================================================================= // Upload Attempt Counter Tests // ============================================================================= TEST_F(AddonUploadTftTest, UploadAttempt_Increment) { - tft_upload_attempt = 0; - tft_upload_attempt++; - EXPECT_EQ(tft_upload_attempt, 1); + tft_upload_attempt = 0; + tft_upload_attempt++; + EXPECT_EQ(tft_upload_attempt, 1); } TEST_F(AddonUploadTftTest, UploadAttempt_MultipleIncrements) { - for (uint8_t i = 0; i < 10; ++i) { - tft_upload_attempt++; - } - EXPECT_EQ(tft_upload_attempt, 10); + for (uint8_t i = 0; i < 10; ++i) { + tft_upload_attempt++; + } + EXPECT_EQ(tft_upload_attempt, 10); } TEST_F(AddonUploadTftTest, UploadAttempt_SetToSpecificValue) { - tft_upload_attempt = 5; - EXPECT_EQ(tft_upload_attempt, 5); + tft_upload_attempt = 5; + EXPECT_EQ(tft_upload_attempt, 5); } TEST_F(AddonUploadTftTest, UploadAttempt_Reset) { - tft_upload_attempt = 10; - tft_upload_attempt = 0; - EXPECT_EQ(tft_upload_attempt, 0); + tft_upload_attempt = 10; + tft_upload_attempt = 0; + EXPECT_EQ(tft_upload_attempt, 0); } TEST_F(AddonUploadTftTest, UploadAttempt_MaxValue) { - tft_upload_attempt = 255; // Max value for uint8_t - EXPECT_EQ(tft_upload_attempt, 255); + tft_upload_attempt = 255; // Max value for uint8_t + EXPECT_EQ(tft_upload_attempt, 255); } TEST_F(AddonUploadTftTest, UploadAttempt_Overflow) { - // Test uint8_t overflow behavior (255 + 1 = 0) - tft_upload_attempt = 255; - tft_upload_attempt++; - EXPECT_EQ(tft_upload_attempt, 0); + // Test uint8_t overflow behavior (255 + 1 = 0) + tft_upload_attempt = 255; + tft_upload_attempt++; + EXPECT_EQ(tft_upload_attempt, 0); } TEST_F(AddonUploadTftTest, UploadAttempt_Decrement) { - tft_upload_attempt = 5; - tft_upload_attempt--; - EXPECT_EQ(tft_upload_attempt, 4); + tft_upload_attempt = 5; + tft_upload_attempt--; + EXPECT_EQ(tft_upload_attempt, 4); } TEST_F(AddonUploadTftTest, UploadAttempt_DecrementFromZero) { - // Test uint8_t underflow behavior (0 - 1 = 255) - tft_upload_attempt = 0; - tft_upload_attempt--; - EXPECT_EQ(tft_upload_attempt, 255); + // Test uint8_t underflow behavior (0 - 1 = 255) + tft_upload_attempt = 0; + tft_upload_attempt--; + EXPECT_EQ(tft_upload_attempt, 255); } TEST_F(AddonUploadTftTest, UploadAttempt_ComparisonOperations) { - tft_upload_attempt = 5; - EXPECT_TRUE(tft_upload_attempt > 0); - EXPECT_TRUE(tft_upload_attempt < 10); - EXPECT_TRUE(tft_upload_attempt >= 5); - EXPECT_TRUE(tft_upload_attempt <= 5); - EXPECT_TRUE(tft_upload_attempt == 5); - EXPECT_TRUE(tft_upload_attempt != 0); + tft_upload_attempt = 5; + EXPECT_TRUE(tft_upload_attempt > 0); + EXPECT_TRUE(tft_upload_attempt < 10); + EXPECT_TRUE(tft_upload_attempt >= 5); + EXPECT_TRUE(tft_upload_attempt <= 5); + EXPECT_TRUE(tft_upload_attempt == 5); + EXPECT_TRUE(tft_upload_attempt != 0); } // ============================================================================= @@ -117,29 +111,29 @@ TEST_F(AddonUploadTftTest, UploadAttempt_ComparisonOperations) { // ============================================================================= TEST_F(AddonUploadTftTest, UploadResult_SetToTrue) { - tft_upload_result = true; - EXPECT_TRUE(tft_upload_result); + tft_upload_result = true; + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, UploadResult_SetToFalse) { - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, UploadResult_Toggle) { - tft_upload_result = false; - tft_upload_result = !tft_upload_result; - EXPECT_TRUE(tft_upload_result); - tft_upload_result = !tft_upload_result; - EXPECT_FALSE(tft_upload_result); + tft_upload_result = false; + tft_upload_result = !tft_upload_result; + EXPECT_TRUE(tft_upload_result); + tft_upload_result = !tft_upload_result; + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, UploadResult_LogicalOperations) { - tft_upload_result = true; - EXPECT_TRUE(tft_upload_result && true); - EXPECT_FALSE(tft_upload_result && false); - EXPECT_TRUE(tft_upload_result || false); - EXPECT_FALSE(!tft_upload_result); + tft_upload_result = true; + EXPECT_TRUE(tft_upload_result && true); + EXPECT_FALSE(tft_upload_result && false); + EXPECT_TRUE(tft_upload_result || false); + EXPECT_FALSE(!tft_upload_result); } // ============================================================================= @@ -147,56 +141,56 @@ TEST_F(AddonUploadTftTest, UploadResult_LogicalOperations) { // ============================================================================= TEST_F(AddonUploadTftTest, CombinedState_InitialState) { - EXPECT_EQ(tft_upload_attempt, 0); - EXPECT_FALSE(tft_upload_result); + EXPECT_EQ(tft_upload_attempt, 0); + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, CombinedState_FirstAttemptFailed) { - tft_upload_attempt = 1; - tft_upload_result = false; - EXPECT_EQ(tft_upload_attempt, 1); - EXPECT_FALSE(tft_upload_result); + tft_upload_attempt = 1; + tft_upload_result = false; + EXPECT_EQ(tft_upload_attempt, 1); + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, CombinedState_FirstAttemptSucceeded) { - tft_upload_attempt = 1; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 1); - EXPECT_TRUE(tft_upload_result); + tft_upload_attempt = 1; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 1); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, CombinedState_MultipleAttemptsBeforeSuccess) { - // Simulate 3 failed attempts - tft_upload_attempt = 1; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + // Simulate 3 failed attempts + tft_upload_attempt = 1; + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); - tft_upload_attempt = 2; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + tft_upload_attempt = 2; + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); - tft_upload_attempt = 3; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + tft_upload_attempt = 3; + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); - // Fourth attempt succeeds - tft_upload_attempt = 4; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 4); - EXPECT_TRUE(tft_upload_result); + // Fourth attempt succeeds + tft_upload_attempt = 4; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 4); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, CombinedState_ResetAfterSuccess) { - // Set to success state - tft_upload_attempt = 3; - tft_upload_result = true; + // Set to success state + tft_upload_attempt = 3; + tft_upload_result = true; - // Reset for new upload - tft_upload_attempt = 0; - tft_upload_result = false; + // Reset for new upload + tft_upload_attempt = 0; + tft_upload_result = false; - EXPECT_EQ(tft_upload_attempt, 0); - EXPECT_FALSE(tft_upload_result); + EXPECT_EQ(tft_upload_attempt, 0); + EXPECT_FALSE(tft_upload_result); } // ============================================================================= @@ -204,27 +198,27 @@ TEST_F(AddonUploadTftTest, CombinedState_ResetAfterSuccess) { // ============================================================================= TEST_F(AddonUploadTftTest, RetryLogic_MaxAttemptsThreshold) { - // Common pattern: retry up to 3 times - const uint8_t MAX_RETRIES = 3; + // Common pattern: retry up to 3 times + const uint8_t MAX_RETRIES = 3; - for (uint8_t i = 0; i < MAX_RETRIES; ++i) { - tft_upload_attempt++; - EXPECT_LE(tft_upload_attempt, MAX_RETRIES); - } + for (uint8_t i = 0; i < MAX_RETRIES; ++i) { + tft_upload_attempt++; + EXPECT_LE(tft_upload_attempt, MAX_RETRIES); + } - EXPECT_EQ(tft_upload_attempt, MAX_RETRIES); + EXPECT_EQ(tft_upload_attempt, MAX_RETRIES); } TEST_F(AddonUploadTftTest, RetryLogic_ExceededMaxAttempts) { - const uint8_t MAX_RETRIES = 5; - tft_upload_attempt = 6; - EXPECT_GT(tft_upload_attempt, MAX_RETRIES); + const uint8_t MAX_RETRIES = 5; + tft_upload_attempt = 6; + EXPECT_GT(tft_upload_attempt, MAX_RETRIES); } TEST_F(AddonUploadTftTest, RetryLogic_WithinMaxAttempts) { - const uint8_t MAX_RETRIES = 10; - tft_upload_attempt = 5; - EXPECT_LT(tft_upload_attempt, MAX_RETRIES); + const uint8_t MAX_RETRIES = 10; + tft_upload_attempt = 5; + EXPECT_LT(tft_upload_attempt, MAX_RETRIES); } // ============================================================================= @@ -232,33 +226,33 @@ TEST_F(AddonUploadTftTest, RetryLogic_WithinMaxAttempts) { // ============================================================================= TEST_F(AddonUploadTftTest, StateMachine_Idle) { - // Idle state: no attempts, no result - EXPECT_EQ(tft_upload_attempt, 0); - EXPECT_FALSE(tft_upload_result); + // Idle state: no attempts, no result + EXPECT_EQ(tft_upload_attempt, 0); + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, StateMachine_InProgress) { - // In progress: attempts > 0, result still false - tft_upload_attempt = 1; - tft_upload_result = false; - EXPECT_GT(tft_upload_attempt, 0); - EXPECT_FALSE(tft_upload_result); + // In progress: attempts > 0, result still false + tft_upload_attempt = 1; + tft_upload_result = false; + EXPECT_GT(tft_upload_attempt, 0); + EXPECT_FALSE(tft_upload_result); } TEST_F(AddonUploadTftTest, StateMachine_Succeeded) { - // Succeeded: result is true - tft_upload_attempt = 2; - tft_upload_result = true; - EXPECT_TRUE(tft_upload_result); + // Succeeded: result is true + tft_upload_attempt = 2; + tft_upload_result = true; + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, StateMachine_Failed) { - // Failed: attempts maxed out, result still false - const uint8_t MAX_ATTEMPTS = 5; - tft_upload_attempt = MAX_ATTEMPTS; - tft_upload_result = false; - EXPECT_EQ(tft_upload_attempt, MAX_ATTEMPTS); - EXPECT_FALSE(tft_upload_result); + // Failed: attempts maxed out, result still false + const uint8_t MAX_ATTEMPTS = 5; + tft_upload_attempt = MAX_ATTEMPTS; + tft_upload_result = false; + EXPECT_EQ(tft_upload_attempt, MAX_ATTEMPTS); + EXPECT_FALSE(tft_upload_result); } // ============================================================================= @@ -266,27 +260,27 @@ TEST_F(AddonUploadTftTest, StateMachine_Failed) { // ============================================================================= TEST_F(AddonUploadTftTest, EdgeCase_ZeroAttemptWithTrueResult) { - // Unusual but valid: success on first try (attempt counter may update after) - tft_upload_attempt = 0; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 0); - EXPECT_TRUE(tft_upload_result); + // Unusual but valid: success on first try (attempt counter may update after) + tft_upload_attempt = 0; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 0); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, EdgeCase_HighAttemptWithTrueResult) { - // Success after many attempts - tft_upload_attempt = 100; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 100); - EXPECT_TRUE(tft_upload_result); + // Success after many attempts + tft_upload_attempt = 100; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 100); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, EdgeCase_MaxAttemptWithFalseResult) { - // Complete failure case - tft_upload_attempt = 255; - tft_upload_result = false; - EXPECT_EQ(tft_upload_attempt, 255); - EXPECT_FALSE(tft_upload_result); + // Complete failure case + tft_upload_attempt = 255; + tft_upload_result = false; + EXPECT_EQ(tft_upload_attempt, 255); + EXPECT_FALSE(tft_upload_result); } // ============================================================================= @@ -294,26 +288,26 @@ TEST_F(AddonUploadTftTest, EdgeCase_MaxAttemptWithFalseResult) { // ============================================================================= TEST_F(AddonUploadTftTest, Independence_AttemptDoesNotAffectResult) { - tft_upload_result = true; - tft_upload_attempt = 100; - EXPECT_TRUE(tft_upload_result); // Result should remain unchanged + tft_upload_result = true; + tft_upload_attempt = 100; + EXPECT_TRUE(tft_upload_result); // Result should remain unchanged } TEST_F(AddonUploadTftTest, Independence_ResultDoesNotAffectAttempt) { - tft_upload_attempt = 42; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 42); // Attempt should remain unchanged + tft_upload_attempt = 42; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 42); // Attempt should remain unchanged } TEST_F(AddonUploadTftTest, Independence_IndependentModification) { - // Modify both independently - tft_upload_attempt = 5; - EXPECT_EQ(tft_upload_attempt, 5); - EXPECT_FALSE(tft_upload_result); // Other variable unchanged + // Modify both independently + tft_upload_attempt = 5; + EXPECT_EQ(tft_upload_attempt, 5); + EXPECT_FALSE(tft_upload_result); // Other variable unchanged - tft_upload_result = true; - EXPECT_TRUE(tft_upload_result); - EXPECT_EQ(tft_upload_attempt, 5); // Other variable unchanged + tft_upload_result = true; + EXPECT_TRUE(tft_upload_result); + EXPECT_EQ(tft_upload_attempt, 5); // Other variable unchanged } // ============================================================================= @@ -321,47 +315,47 @@ TEST_F(AddonUploadTftTest, Independence_IndependentModification) { // ============================================================================= TEST_F(AddonUploadTftTest, TypicalUsage_SingleSuccessfulUpload) { - // Start upload - tft_upload_attempt = 1; - tft_upload_result = false; + // Start upload + tft_upload_attempt = 1; + tft_upload_result = false; - // Upload completes successfully - tft_upload_result = true; + // Upload completes successfully + tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 1); - EXPECT_TRUE(tft_upload_result); + EXPECT_EQ(tft_upload_attempt, 1); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, TypicalUsage_RetryThenSuccess) { - // First attempt - tft_upload_attempt = 1; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + // First attempt + tft_upload_attempt = 1; + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); - // Second attempt (retry) - tft_upload_attempt = 2; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); + // Second attempt (retry) + tft_upload_attempt = 2; + tft_upload_result = false; + EXPECT_FALSE(tft_upload_result); - // Third attempt succeeds - tft_upload_attempt = 3; - tft_upload_result = true; - EXPECT_TRUE(tft_upload_result); - EXPECT_EQ(tft_upload_attempt, 3); + // Third attempt succeeds + tft_upload_attempt = 3; + tft_upload_result = true; + EXPECT_TRUE(tft_upload_result); + EXPECT_EQ(tft_upload_attempt, 3); } TEST_F(AddonUploadTftTest, TypicalUsage_AllAttemptsExhausted) { - const uint8_t MAX_RETRIES = 3; - - for (uint8_t i = 1; i <= MAX_RETRIES; ++i) { - tft_upload_attempt = i; - tft_upload_result = false; - EXPECT_FALSE(tft_upload_result); - } + const uint8_t MAX_RETRIES = 3; - // All attempts exhausted - EXPECT_EQ(tft_upload_attempt, MAX_RETRIES); + for (uint8_t i = 1; i <= MAX_RETRIES; ++i) { + tft_upload_attempt = i; + tft_upload_result = false; EXPECT_FALSE(tft_upload_result); + } + + // All attempts exhausted + EXPECT_EQ(tft_upload_attempt, MAX_RETRIES); + EXPECT_FALSE(tft_upload_result); } // ============================================================================= @@ -369,41 +363,41 @@ TEST_F(AddonUploadTftTest, TypicalUsage_AllAttemptsExhausted) { // ============================================================================= TEST_F(AddonUploadTftTest, Regression_NoUnexpectedSideEffects) { - // Set values - tft_upload_attempt = 10; - tft_upload_result = true; + // Set values + tft_upload_attempt = 10; + tft_upload_result = true; - // Read values multiple times - should be stable - for (int i = 0; i < 5; ++i) { - EXPECT_EQ(tft_upload_attempt, 10); - EXPECT_TRUE(tft_upload_result); - } + // Read values multiple times - should be stable + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(tft_upload_attempt, 10); + EXPECT_TRUE(tft_upload_result); + } } TEST_F(AddonUploadTftTest, Regression_ThreadSafeAssumption) { - // Note: These variables are not thread-safe by default - // This test documents that they're simple globals - // For thread-safety, external synchronization would be needed - tft_upload_attempt = 7; - tft_upload_result = true; - EXPECT_EQ(tft_upload_attempt, 7); - EXPECT_TRUE(tft_upload_result); + // Note: These variables are not thread-safe by default + // This test documents that they're simple globals + // For thread-safety, external synchronization would be needed + tft_upload_attempt = 7; + tft_upload_result = true; + EXPECT_EQ(tft_upload_attempt, 7); + EXPECT_TRUE(tft_upload_result); } TEST_F(AddonUploadTftTest, Regression_NoMemoryLeaks) { - // These are simple value types, no dynamic allocation - // This test documents that behavior - for (int i = 0; i < 1000; ++i) { - tft_upload_attempt = i % 256; - tft_upload_result = (i % 2 == 0); - } - // If we got here without crashing, no memory issues - SUCCEED(); + // These are simple value types, no dynamic allocation + // This test documents that behavior + for (int i = 0; i < 1000; ++i) { + tft_upload_attempt = i % 256; + tft_upload_result = (i % 2 == 0); + } + // If we got here without crashing, no memory issues + SUCCEED(); } -} // namespace nspanel_easy +} // namespace nspanel_easy int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } \ No newline at end of file diff --git a/.test/unit/test_base.cpp b/.test/unit/test_base.cpp index e3ebb386..bfc2cd58 100644 --- a/.test/unit/test_base.cpp +++ b/.test/unit/test_base.cpp @@ -10,25 +10,24 @@ // Mock ESPHome components namespace esphome { - namespace api { - class CustomAPIDevice { - public: - void fire_homeassistant_event(const std::string& event_name, - const std::map& data) { - // Mock implementation - } - }; - } +namespace api { +class CustomAPIDevice { + public: + void fire_homeassistant_event(const std::string &event_name, const std::map &data) { + // Mock implementation + } +}; +} // namespace api - class App_ { - public: - void feed_wdt() {} - }; +class App_ { + public: + void feed_wdt() {} +}; - extern App_ App; +extern App_ App; - void delay(uint32_t ms) {} -} +void delay(uint32_t ms) {} +} // namespace esphome esphome::App_ esphome::App; @@ -44,20 +43,20 @@ esphome::App_ esphome::App; namespace nspanel_easy { class BaseTest : public ::testing::Test { -protected: - void SetUp() override { - // Reset global flags before each test - system_flags = SystemFlags(); - blueprint_status_flags = BlueprintStatusFlags(); - cached_device_name = ""; - } - - void TearDown() override { - // Cleanup after each test - system_flags = SystemFlags(); - blueprint_status_flags = BlueprintStatusFlags(); - cached_device_name = ""; - } + protected: + void SetUp() override { + // Reset global flags before each test + system_flags = SystemFlags(); + blueprint_status_flags = BlueprintStatusFlags(); + cached_device_name = ""; + } + + void TearDown() override { + // Cleanup after each test + system_flags = SystemFlags(); + blueprint_status_flags = BlueprintStatusFlags(); + cached_device_name = ""; + } }; // ============================================================================= @@ -65,90 +64,90 @@ class BaseTest : public ::testing::Test { // ============================================================================= TEST_F(BaseTest, SystemFlags_DefaultConstructor) { - SystemFlags flags; - EXPECT_FALSE(flags.wifi_ready); - EXPECT_FALSE(flags.api_ready); - EXPECT_FALSE(flags.baud_rate_set); - EXPECT_FALSE(flags.nextion_ready); - EXPECT_FALSE(flags.blueprint_ready); - EXPECT_FALSE(flags.tft_ready); - EXPECT_FALSE(flags.boot_completed); - EXPECT_FALSE(flags.version_check_ok); - EXPECT_FALSE(flags.display_settings_received); - EXPECT_FALSE(flags.tft_upload_active); - EXPECT_FALSE(flags.safe_mode_active); - EXPECT_FALSE(flags.ota_in_progress); - EXPECT_FALSE(flags.display_sleep); - EXPECT_EQ(flags.reserved, 0); + SystemFlags flags; + EXPECT_FALSE(flags.wifi_ready); + EXPECT_FALSE(flags.api_ready); + EXPECT_FALSE(flags.baud_rate_set); + EXPECT_FALSE(flags.nextion_ready); + EXPECT_FALSE(flags.blueprint_ready); + EXPECT_FALSE(flags.tft_ready); + EXPECT_FALSE(flags.boot_completed); + EXPECT_FALSE(flags.version_check_ok); + EXPECT_FALSE(flags.display_settings_received); + EXPECT_FALSE(flags.tft_upload_active); + EXPECT_FALSE(flags.safe_mode_active); + EXPECT_FALSE(flags.ota_in_progress); + EXPECT_FALSE(flags.display_sleep); + EXPECT_EQ(flags.reserved, 0); } TEST_F(BaseTest, SystemFlags_SizeOptimization) { - // Should pack into 2 bytes (uint16_t) - EXPECT_EQ(sizeof(SystemFlags), sizeof(uint16_t)); + // Should pack into 2 bytes (uint16_t) + EXPECT_EQ(sizeof(SystemFlags), sizeof(uint16_t)); } TEST_F(BaseTest, SystemFlags_IndividualFlagSet) { - SystemFlags flags; - flags.wifi_ready = true; - EXPECT_TRUE(flags.wifi_ready); - EXPECT_FALSE(flags.api_ready); // Other flags should remain false + SystemFlags flags; + flags.wifi_ready = true; + EXPECT_TRUE(flags.wifi_ready); + EXPECT_FALSE(flags.api_ready); // Other flags should remain false } TEST_F(BaseTest, SystemFlags_MultipleFlagsSet) { - SystemFlags flags; - flags.wifi_ready = true; - flags.api_ready = true; - flags.boot_completed = true; + SystemFlags flags; + flags.wifi_ready = true; + flags.api_ready = true; + flags.boot_completed = true; - EXPECT_TRUE(flags.wifi_ready); - EXPECT_TRUE(flags.api_ready); - EXPECT_TRUE(flags.boot_completed); - EXPECT_FALSE(flags.tft_ready); + EXPECT_TRUE(flags.wifi_ready); + EXPECT_TRUE(flags.api_ready); + EXPECT_TRUE(flags.boot_completed); + EXPECT_FALSE(flags.tft_ready); } TEST_F(BaseTest, SystemFlags_FlagToggle) { - SystemFlags flags; - flags.wifi_ready = true; - EXPECT_TRUE(flags.wifi_ready); + SystemFlags flags; + flags.wifi_ready = true; + EXPECT_TRUE(flags.wifi_ready); - flags.wifi_ready = false; - EXPECT_FALSE(flags.wifi_ready); + flags.wifi_ready = false; + EXPECT_FALSE(flags.wifi_ready); } TEST_F(BaseTest, SystemFlags_AllBootFlagsSet) { - SystemFlags flags; - flags.wifi_ready = true; - flags.api_ready = true; - flags.baud_rate_set = true; - flags.nextion_ready = true; - flags.blueprint_ready = true; - flags.tft_ready = true; - flags.boot_completed = true; - flags.version_check_ok = true; - flags.display_settings_received = true; - - EXPECT_TRUE(flags.wifi_ready); - EXPECT_TRUE(flags.api_ready); - EXPECT_TRUE(flags.baud_rate_set); - EXPECT_TRUE(flags.nextion_ready); - EXPECT_TRUE(flags.blueprint_ready); - EXPECT_TRUE(flags.tft_ready); - EXPECT_TRUE(flags.boot_completed); - EXPECT_TRUE(flags.version_check_ok); - EXPECT_TRUE(flags.display_settings_received); + SystemFlags flags; + flags.wifi_ready = true; + flags.api_ready = true; + flags.baud_rate_set = true; + flags.nextion_ready = true; + flags.blueprint_ready = true; + flags.tft_ready = true; + flags.boot_completed = true; + flags.version_check_ok = true; + flags.display_settings_received = true; + + EXPECT_TRUE(flags.wifi_ready); + EXPECT_TRUE(flags.api_ready); + EXPECT_TRUE(flags.baud_rate_set); + EXPECT_TRUE(flags.nextion_ready); + EXPECT_TRUE(flags.blueprint_ready); + EXPECT_TRUE(flags.tft_ready); + EXPECT_TRUE(flags.boot_completed); + EXPECT_TRUE(flags.version_check_ok); + EXPECT_TRUE(flags.display_settings_received); } TEST_F(BaseTest, SystemFlags_RuntimeOperationFlags) { - SystemFlags flags; - flags.tft_upload_active = true; - flags.safe_mode_active = true; - flags.ota_in_progress = true; - flags.display_sleep = true; + SystemFlags flags; + flags.tft_upload_active = true; + flags.safe_mode_active = true; + flags.ota_in_progress = true; + flags.display_sleep = true; - EXPECT_TRUE(flags.tft_upload_active); - EXPECT_TRUE(flags.safe_mode_active); - EXPECT_TRUE(flags.ota_in_progress); - EXPECT_TRUE(flags.display_sleep); + EXPECT_TRUE(flags.tft_upload_active); + EXPECT_TRUE(flags.safe_mode_active); + EXPECT_TRUE(flags.ota_in_progress); + EXPECT_TRUE(flags.display_sleep); } // ============================================================================= @@ -156,138 +155,138 @@ TEST_F(BaseTest, SystemFlags_RuntimeOperationFlags) { // ============================================================================= TEST_F(BaseTest, BlueprintStatusFlags_DefaultConstructor) { - BlueprintStatusFlags flags; - EXPECT_FALSE(flags.page_home); - EXPECT_FALSE(flags.page_qrcode); - EXPECT_FALSE(flags.relay_settings); - EXPECT_FALSE(flags.version); - EXPECT_FALSE(flags.hw_buttons_settings); - EXPECT_FALSE(flags.page_utilities); - EXPECT_EQ(flags.reserved, 0); + BlueprintStatusFlags flags; + EXPECT_FALSE(flags.page_home); + EXPECT_FALSE(flags.page_qrcode); + EXPECT_FALSE(flags.relay_settings); + EXPECT_FALSE(flags.version); + EXPECT_FALSE(flags.hw_buttons_settings); + EXPECT_FALSE(flags.page_utilities); + EXPECT_EQ(flags.reserved, 0); } TEST_F(BaseTest, BlueprintStatusFlags_SizeOptimization) { - // Should pack into 1 byte (uint8_t) - EXPECT_EQ(sizeof(BlueprintStatusFlags), sizeof(uint8_t)); + // Should pack into 1 byte (uint8_t) + EXPECT_EQ(sizeof(BlueprintStatusFlags), sizeof(uint8_t)); } TEST_F(BaseTest, BlueprintStatusFlags_AllActiveFlagsSetFalse) { - BlueprintStatusFlags flags; - EXPECT_FALSE(flags.all_active_flags_set()); + BlueprintStatusFlags flags; + EXPECT_FALSE(flags.all_active_flags_set()); } TEST_F(BaseTest, BlueprintStatusFlags_AllActiveFlagsSetTrue) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - flags.version = true; - flags.hw_buttons_settings = true; - flags.page_utilities = true; + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + flags.version = true; + flags.hw_buttons_settings = true; + flags.page_utilities = true; - EXPECT_TRUE(flags.all_active_flags_set()); + EXPECT_TRUE(flags.all_active_flags_set()); } TEST_F(BaseTest, BlueprintStatusFlags_AllActiveFlagsSetPartial) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - // Missing: version, hw_buttons_settings, page_utilities + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + // Missing: version, hw_buttons_settings, page_utilities - EXPECT_FALSE(flags.all_active_flags_set()); + EXPECT_FALSE(flags.all_active_flags_set()); } TEST_F(BaseTest, BlueprintStatusFlags_CountActiveFlagsSetZero) { - BlueprintStatusFlags flags; - EXPECT_EQ(flags.count_active_flags_set(), 0); + BlueprintStatusFlags flags; + EXPECT_EQ(flags.count_active_flags_set(), 0); } TEST_F(BaseTest, BlueprintStatusFlags_CountActiveFlagsSetAll) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - flags.version = true; - flags.hw_buttons_settings = true; - flags.page_utilities = true; + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + flags.version = true; + flags.hw_buttons_settings = true; + flags.page_utilities = true; - EXPECT_EQ(flags.count_active_flags_set(), 6); + EXPECT_EQ(flags.count_active_flags_set(), 6); } TEST_F(BaseTest, BlueprintStatusFlags_CountActiveFlagsSetPartial) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.relay_settings = true; - flags.version = true; + BlueprintStatusFlags flags; + flags.page_home = true; + flags.relay_settings = true; + flags.version = true; - EXPECT_EQ(flags.count_active_flags_set(), 3); + EXPECT_EQ(flags.count_active_flags_set(), 3); } TEST_F(BaseTest, BlueprintStatusFlags_CompletionPercentageZero) { - BlueprintStatusFlags flags; - EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 0.0f); + BlueprintStatusFlags flags; + EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 0.0f); } TEST_F(BaseTest, BlueprintStatusFlags_CompletionPercentageFull) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - flags.version = true; - flags.hw_buttons_settings = true; - flags.page_utilities = true; + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + flags.version = true; + flags.hw_buttons_settings = true; + flags.page_utilities = true; - EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 100.0f); + EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 100.0f); } TEST_F(BaseTest, BlueprintStatusFlags_CompletionPercentageHalf) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - // 3 out of 6 = 50% + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + // 3 out of 6 = 50% - EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 50.0f); + EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 50.0f); } TEST_F(BaseTest, BlueprintStatusFlags_CompletionPercentageThird) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - // 2 out of 6 = 33.333...% + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + // 2 out of 6 = 33.333...% - EXPECT_NEAR(flags.get_completion_percentage(), 33.333f, 0.01f); + EXPECT_NEAR(flags.get_completion_percentage(), 33.333f, 0.01f); } TEST_F(BaseTest, BlueprintStatusFlags_Reset) { - BlueprintStatusFlags flags; - flags.page_home = true; - flags.page_qrcode = true; - flags.relay_settings = true; - flags.version = true; - flags.hw_buttons_settings = true; - flags.page_utilities = true; + BlueprintStatusFlags flags; + flags.page_home = true; + flags.page_qrcode = true; + flags.relay_settings = true; + flags.version = true; + flags.hw_buttons_settings = true; + flags.page_utilities = true; - flags.reset(); + flags.reset(); - EXPECT_FALSE(flags.page_home); - EXPECT_FALSE(flags.page_qrcode); - EXPECT_FALSE(flags.relay_settings); - EXPECT_FALSE(flags.version); - EXPECT_FALSE(flags.hw_buttons_settings); - EXPECT_FALSE(flags.page_utilities); - EXPECT_EQ(flags.reserved, 0); + EXPECT_FALSE(flags.page_home); + EXPECT_FALSE(flags.page_qrcode); + EXPECT_FALSE(flags.relay_settings); + EXPECT_FALSE(flags.version); + EXPECT_FALSE(flags.hw_buttons_settings); + EXPECT_FALSE(flags.page_utilities); + EXPECT_EQ(flags.reserved, 0); } TEST_F(BaseTest, BlueprintStatusFlags_ReservedNotCountedInPercentage) { - BlueprintStatusFlags flags; - flags.reserved = 3; // Set reserved bits + BlueprintStatusFlags flags; + flags.reserved = 3; // Set reserved bits - // Reserved bits should not affect percentage - EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 0.0f); - EXPECT_EQ(flags.count_active_flags_set(), 0); - EXPECT_FALSE(flags.all_active_flags_set()); + // Reserved bits should not affect percentage + EXPECT_FLOAT_EQ(flags.get_completion_percentage(), 0.0f); + EXPECT_EQ(flags.count_active_flags_set(), 0); + EXPECT_FALSE(flags.all_active_flags_set()); } // ============================================================================= @@ -295,170 +294,152 @@ TEST_F(BaseTest, BlueprintStatusFlags_ReservedNotCountedInPercentage) { // ============================================================================= TEST_F(BaseTest, GlobalSystemFlags_Accessible) { - system_flags.wifi_ready = true; - EXPECT_TRUE(system_flags.wifi_ready); + system_flags.wifi_ready = true; + EXPECT_TRUE(system_flags.wifi_ready); } TEST_F(BaseTest, GlobalBlueprintStatusFlags_Accessible) { - blueprint_status_flags.page_home = true; - EXPECT_TRUE(blueprint_status_flags.page_home); + blueprint_status_flags.page_home = true; + EXPECT_TRUE(blueprint_status_flags.page_home); } TEST_F(BaseTest, GlobalCachedDeviceName_Accessible) { - cached_device_name = "test_device"; - EXPECT_EQ(cached_device_name, "test_device"); + cached_device_name = "test_device"; + EXPECT_EQ(cached_device_name, "test_device"); } -TEST_F(BaseTest, GlobalCachedDeviceName_DefaultEmpty) { - EXPECT_TRUE(cached_device_name.empty()); -} +TEST_F(BaseTest, GlobalCachedDeviceName_DefaultEmpty) { EXPECT_TRUE(cached_device_name.empty()); } // ============================================================================= // is_device_ready_for_tasks() Tests // ============================================================================= TEST_F(BaseTest, IsDeviceReadyForTasks_BootNotCompleted) { - system_flags.boot_completed = false; - EXPECT_FALSE(is_device_ready_for_tasks()); + system_flags.boot_completed = false; + EXPECT_FALSE(is_device_ready_for_tasks()); } TEST_F(BaseTest, IsDeviceReadyForTasks_OtaInProgress) { - system_flags.boot_completed = true; - system_flags.ota_in_progress = true; - EXPECT_FALSE(is_device_ready_for_tasks()); + system_flags.boot_completed = true; + system_flags.ota_in_progress = true; + EXPECT_FALSE(is_device_ready_for_tasks()); } TEST_F(BaseTest, IsDeviceReadyForTasks_TftUploadActive) { - system_flags.boot_completed = true; - system_flags.tft_upload_active = true; - EXPECT_FALSE(is_device_ready_for_tasks()); + system_flags.boot_completed = true; + system_flags.tft_upload_active = true; + EXPECT_FALSE(is_device_ready_for_tasks()); } TEST_F(BaseTest, IsDeviceReadyForTasks_SafeModeActive) { - system_flags.boot_completed = true; - system_flags.safe_mode_active = true; - EXPECT_FALSE(is_device_ready_for_tasks()); + system_flags.boot_completed = true; + system_flags.safe_mode_active = true; + EXPECT_FALSE(is_device_ready_for_tasks()); } TEST_F(BaseTest, IsDeviceReadyForTasks_AllConditionsMet) { - system_flags.boot_completed = true; - system_flags.ota_in_progress = false; - system_flags.tft_upload_active = false; - system_flags.safe_mode_active = false; - EXPECT_TRUE(is_device_ready_for_tasks()); + system_flags.boot_completed = true; + system_flags.ota_in_progress = false; + system_flags.tft_upload_active = false; + system_flags.safe_mode_active = false; + EXPECT_TRUE(is_device_ready_for_tasks()); } TEST_F(BaseTest, IsDeviceReadyForTasks_MultipleBlockingOperations) { - system_flags.boot_completed = true; - system_flags.ota_in_progress = true; - system_flags.tft_upload_active = true; - system_flags.safe_mode_active = true; - EXPECT_FALSE(is_device_ready_for_tasks()); + system_flags.boot_completed = true; + system_flags.ota_in_progress = true; + system_flags.tft_upload_active = true; + system_flags.safe_mode_active = true; + EXPECT_FALSE(is_device_ready_for_tasks()); } // ============================================================================= // is_blueprint_fully_ready() Tests // ============================================================================= -TEST_F(BaseTest, IsBlueprintFullyReady_NoFlagsSet) { - EXPECT_FALSE(is_blueprint_fully_ready()); -} +TEST_F(BaseTest, IsBlueprintFullyReady_NoFlagsSet) { EXPECT_FALSE(is_blueprint_fully_ready()); } TEST_F(BaseTest, IsBlueprintFullyReady_AllFlagsSet) { - blueprint_status_flags.page_home = true; - blueprint_status_flags.page_qrcode = true; - blueprint_status_flags.relay_settings = true; - blueprint_status_flags.version = true; - blueprint_status_flags.hw_buttons_settings = true; - blueprint_status_flags.page_utilities = true; + blueprint_status_flags.page_home = true; + blueprint_status_flags.page_qrcode = true; + blueprint_status_flags.relay_settings = true; + blueprint_status_flags.version = true; + blueprint_status_flags.hw_buttons_settings = true; + blueprint_status_flags.page_utilities = true; - EXPECT_TRUE(is_blueprint_fully_ready()); + EXPECT_TRUE(is_blueprint_fully_ready()); } TEST_F(BaseTest, IsBlueprintFullyReady_UpdatesSystemFlag) { - blueprint_status_flags.page_home = true; - blueprint_status_flags.page_qrcode = true; - blueprint_status_flags.relay_settings = true; - blueprint_status_flags.version = true; - blueprint_status_flags.hw_buttons_settings = true; - blueprint_status_flags.page_utilities = true; + blueprint_status_flags.page_home = true; + blueprint_status_flags.page_qrcode = true; + blueprint_status_flags.relay_settings = true; + blueprint_status_flags.version = true; + blueprint_status_flags.hw_buttons_settings = true; + blueprint_status_flags.page_utilities = true; - EXPECT_FALSE(system_flags.blueprint_ready); // Initially false + EXPECT_FALSE(system_flags.blueprint_ready); // Initially false - is_blueprint_fully_ready(); + is_blueprint_fully_ready(); - EXPECT_TRUE(system_flags.blueprint_ready); // Updated to true + EXPECT_TRUE(system_flags.blueprint_ready); // Updated to true } TEST_F(BaseTest, IsBlueprintFullyReady_PartialFlagsSet) { - blueprint_status_flags.page_home = true; - blueprint_status_flags.page_qrcode = true; + blueprint_status_flags.page_home = true; + blueprint_status_flags.page_qrcode = true; - EXPECT_FALSE(is_blueprint_fully_ready()); - EXPECT_FALSE(system_flags.blueprint_ready); + EXPECT_FALSE(is_blueprint_fully_ready()); + EXPECT_FALSE(system_flags.blueprint_ready); } // ============================================================================= // feed_wdt_delay() Tests // ============================================================================= -TEST_F(BaseTest, FeedWdtDelay_DefaultParameter) { - EXPECT_NO_THROW(feed_wdt_delay()); -} +TEST_F(BaseTest, FeedWdtDelay_DefaultParameter) { EXPECT_NO_THROW(feed_wdt_delay()); } -TEST_F(BaseTest, FeedWdtDelay_CustomParameter) { - EXPECT_NO_THROW(feed_wdt_delay(10)); -} +TEST_F(BaseTest, FeedWdtDelay_CustomParameter) { EXPECT_NO_THROW(feed_wdt_delay(10)); } -TEST_F(BaseTest, FeedWdtDelay_ZeroDelay) { - EXPECT_NO_THROW(feed_wdt_delay(0)); -} +TEST_F(BaseTest, FeedWdtDelay_ZeroDelay) { EXPECT_NO_THROW(feed_wdt_delay(0)); } -TEST_F(BaseTest, FeedWdtDelay_LargeDelay) { - EXPECT_NO_THROW(feed_wdt_delay(1000)); -} +TEST_F(BaseTest, FeedWdtDelay_LargeDelay) { EXPECT_NO_THROW(feed_wdt_delay(1000)); } // ============================================================================= // fire_ha_event() Tests // ============================================================================= TEST_F(BaseTest, FireHaEvent_SimpleEvent) { - cached_device_name = "test_panel"; - EXPECT_NO_THROW(fire_ha_event("test_event")); + cached_device_name = "test_panel"; + EXPECT_NO_THROW(fire_ha_event("test_event")); } TEST_F(BaseTest, FireHaEvent_WithData) { - cached_device_name = "test_panel"; - std::map data = { - {"key1", "value1"}, - {"key2", "value2"} - }; - EXPECT_NO_THROW(fire_ha_event("test_event", data)); + cached_device_name = "test_panel"; + std::map data = {{"key1", "value1"}, {"key2", "value2"}}; + EXPECT_NO_THROW(fire_ha_event("test_event", data)); } TEST_F(BaseTest, FireHaEvent_EmptyDeviceName) { - cached_device_name = ""; - EXPECT_NO_THROW(fire_ha_event("test_event")); + cached_device_name = ""; + EXPECT_NO_THROW(fire_ha_event("test_event")); } TEST_F(BaseTest, FireHaEvent_EmptyEventType) { - cached_device_name = "test_panel"; - EXPECT_NO_THROW(fire_ha_event("")); + cached_device_name = "test_panel"; + EXPECT_NO_THROW(fire_ha_event("")); } TEST_F(BaseTest, FireHaEvent_EmptyData) { - cached_device_name = "test_panel"; - std::map empty_data; - EXPECT_NO_THROW(fire_ha_event("test_event", empty_data)); + cached_device_name = "test_panel"; + std::map empty_data; + EXPECT_NO_THROW(fire_ha_event("test_event", empty_data)); } TEST_F(BaseTest, FireHaEvent_SpecialCharactersInData) { - cached_device_name = "test_panel"; - std::map data = { - {"special", "!@#$%^&*()"}, - {"unicode", "héllo wörld"} - }; - EXPECT_NO_THROW(fire_ha_event("test_event", data)); + cached_device_name = "test_panel"; + std::map data = {{"special", "!@#$%^&*()"}, {"unicode", "héllo wörld"}}; + EXPECT_NO_THROW(fire_ha_event("test_event", data)); } // ============================================================================= @@ -466,62 +447,62 @@ TEST_F(BaseTest, FireHaEvent_SpecialCharactersInData) { // ============================================================================= TEST_F(BaseTest, SystemFlags_AllFlagsSetAndUnset) { - system_flags.wifi_ready = true; - system_flags.api_ready = true; - system_flags.baud_rate_set = true; - system_flags.nextion_ready = true; - system_flags.blueprint_ready = true; - system_flags.tft_ready = true; - system_flags.boot_completed = true; - system_flags.version_check_ok = true; - system_flags.display_settings_received = true; - system_flags.tft_upload_active = true; - system_flags.safe_mode_active = true; - system_flags.ota_in_progress = true; - system_flags.display_sleep = true; - - // All flags should be true - EXPECT_TRUE(system_flags.wifi_ready); - EXPECT_TRUE(system_flags.boot_completed); - - // Reset all - system_flags = SystemFlags(); - EXPECT_FALSE(system_flags.wifi_ready); - EXPECT_FALSE(system_flags.boot_completed); + system_flags.wifi_ready = true; + system_flags.api_ready = true; + system_flags.baud_rate_set = true; + system_flags.nextion_ready = true; + system_flags.blueprint_ready = true; + system_flags.tft_ready = true; + system_flags.boot_completed = true; + system_flags.version_check_ok = true; + system_flags.display_settings_received = true; + system_flags.tft_upload_active = true; + system_flags.safe_mode_active = true; + system_flags.ota_in_progress = true; + system_flags.display_sleep = true; + + // All flags should be true + EXPECT_TRUE(system_flags.wifi_ready); + EXPECT_TRUE(system_flags.boot_completed); + + // Reset all + system_flags = SystemFlags(); + EXPECT_FALSE(system_flags.wifi_ready); + EXPECT_FALSE(system_flags.boot_completed); } TEST_F(BaseTest, BlueprintStatusFlags_IncrementalCompletion) { - EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 0.0f); + EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 0.0f); - blueprint_status_flags.page_home = true; - EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 16.667f, 0.01f); + blueprint_status_flags.page_home = true; + EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 16.667f, 0.01f); - blueprint_status_flags.page_qrcode = true; - EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 33.333f, 0.01f); + blueprint_status_flags.page_qrcode = true; + EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 33.333f, 0.01f); - blueprint_status_flags.relay_settings = true; - EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 50.0f); + blueprint_status_flags.relay_settings = true; + EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 50.0f); - blueprint_status_flags.version = true; - EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 66.667f, 0.01f); + blueprint_status_flags.version = true; + EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 66.667f, 0.01f); - blueprint_status_flags.hw_buttons_settings = true; - EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 83.333f, 0.01f); + blueprint_status_flags.hw_buttons_settings = true; + EXPECT_NEAR(blueprint_status_flags.get_completion_percentage(), 83.333f, 0.01f); - blueprint_status_flags.page_utilities = true; - EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 100.0f); + blueprint_status_flags.page_utilities = true; + EXPECT_FLOAT_EQ(blueprint_status_flags.get_completion_percentage(), 100.0f); } TEST_F(BaseTest, CachedDeviceName_LongString) { - std::string long_name(1000, 'A'); - cached_device_name = long_name; - EXPECT_EQ(cached_device_name.length(), 1000); - EXPECT_EQ(cached_device_name, long_name); + std::string long_name(1000, 'A'); + cached_device_name = long_name; + EXPECT_EQ(cached_device_name.length(), 1000); + EXPECT_EQ(cached_device_name, long_name); } TEST_F(BaseTest, CachedDeviceName_SpecialCharacters) { - cached_device_name = "device-name_123!@#"; - EXPECT_EQ(cached_device_name, "device-name_123!@#"); + cached_device_name = "device-name_123!@#"; + EXPECT_EQ(cached_device_name, "device-name_123!@#"); } // ============================================================================= @@ -529,35 +510,35 @@ TEST_F(BaseTest, CachedDeviceName_SpecialCharacters) { // ============================================================================= TEST_F(BaseTest, Regression_FlagsIndependence) { - // Setting one flag should not affect others - system_flags.wifi_ready = true; - EXPECT_TRUE(system_flags.wifi_ready); - EXPECT_FALSE(system_flags.api_ready); - EXPECT_FALSE(system_flags.boot_completed); + // Setting one flag should not affect others + system_flags.wifi_ready = true; + EXPECT_TRUE(system_flags.wifi_ready); + EXPECT_FALSE(system_flags.api_ready); + EXPECT_FALSE(system_flags.boot_completed); } TEST_F(BaseTest, Regression_BlueprintPercentageAccuracy) { - // Verify percentage calculation is accurate - BlueprintStatusFlags flags; - flags.page_home = true; // 1/6 - EXPECT_NEAR(flags.get_completion_percentage(), 16.6667f, 0.01f); + // Verify percentage calculation is accurate + BlueprintStatusFlags flags; + flags.page_home = true; // 1/6 + EXPECT_NEAR(flags.get_completion_percentage(), 16.6667f, 0.01f); } TEST_F(BaseTest, Regression_NoMemoryCorruption) { - // Repeatedly set and reset flags - for (int i = 0; i < 1000; ++i) { - system_flags.wifi_ready = true; - system_flags.wifi_ready = false; - blueprint_status_flags.page_home = true; - blueprint_status_flags.reset(); - } - EXPECT_FALSE(system_flags.wifi_ready); - EXPECT_FALSE(blueprint_status_flags.page_home); + // Repeatedly set and reset flags + for (int i = 0; i < 1000; ++i) { + system_flags.wifi_ready = true; + system_flags.wifi_ready = false; + blueprint_status_flags.page_home = true; + blueprint_status_flags.reset(); + } + EXPECT_FALSE(system_flags.wifi_ready); + EXPECT_FALSE(blueprint_status_flags.page_home); } -} // namespace nspanel_easy +} // namespace nspanel_easy int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } \ No newline at end of file diff --git a/components/nspanel_easy/addon_climate.cpp b/components/nspanel_easy/addon_climate.cpp index 21c0d528..d4ed7c5f 100644 --- a/components/nspanel_easy/addon_climate.cpp +++ b/components/nspanel_easy/addon_climate.cpp @@ -7,11 +7,11 @@ namespace esphome { namespace nspanel_easy { - // Global var for the friendly name of the embedded climate entity - std::string addon_climate_friendly_name = "Thermostat"; - bool is_addon_climate_visible = false; +// Global var for the friendly name of the embedded climate entity +std::string addon_climate_friendly_name = "Thermostat"; +bool is_addon_climate_visible = false; } // namespace nspanel_easy } // namespace esphome -#endif //NSPANEL_EASY_ADDON_CLIMATE_BASE +#endif // NSPANEL_EASY_ADDON_CLIMATE_BASE diff --git a/components/nspanel_easy/addon_climate.h b/components/nspanel_easy/addon_climate.h index caabe49d..cc0e2d4f 100644 --- a/components/nspanel_easy/addon_climate.h +++ b/components/nspanel_easy/addon_climate.h @@ -10,118 +10,118 @@ namespace esphome { namespace nspanel_easy { - // Global var for the friendly name of the embedded climate entity - extern std::string addon_climate_friendly_name; +// Global var for the friendly name of the embedded climate entity +extern std::string addon_climate_friendly_name; - extern bool is_addon_climate_visible; +extern bool is_addon_climate_visible; - // ============================================================================= - // Climate Enumerations - // ============================================================================= +// ============================================================================= +// Climate Enumerations +// ============================================================================= - /** - * @enum ClimateAction - * @brief Current action of the climate device. - * - * Represents what the climate device is currently doing. - * Values match ESPHome's climate component definitions. - */ - enum ClimateAction : uint8_t { - CLIMATE_ACTION_OFF = 0, ///< Climate device is off (inactive or no power) - CLIMATE_ACTION_COOLING = 2, ///< Climate device is actively cooling - CLIMATE_ACTION_HEATING = 3, ///< Climate device is actively heating - CLIMATE_ACTION_IDLE = 4, ///< Climate device is idle (monitoring but no action needed) - CLIMATE_ACTION_DRYING = 5, ///< Climate device is drying - CLIMATE_ACTION_FAN = 6 ///< Climate device is in fan only mode - }; +/** + * @enum ClimateAction + * @brief Current action of the climate device. + * + * Represents what the climate device is currently doing. + * Values match ESPHome's climate component definitions. + */ +enum ClimateAction : uint8_t { + CLIMATE_ACTION_OFF = 0, ///< Climate device is off (inactive or no power) + CLIMATE_ACTION_COOLING = 2, ///< Climate device is actively cooling + CLIMATE_ACTION_HEATING = 3, ///< Climate device is actively heating + CLIMATE_ACTION_IDLE = 4, ///< Climate device is idle (monitoring but no action needed) + CLIMATE_ACTION_DRYING = 5, ///< Climate device is drying + CLIMATE_ACTION_FAN = 6 ///< Climate device is in fan only mode +}; - /** - * @enum ClimateMode - * @brief Mode the climate device is set to. - * - * Represents the operating mode selected for the climate device. - * Values match ESPHome's climate component definitions. - */ - enum ClimateMode : uint8_t { - CLIMATE_MODE_OFF = 0, ///< Climate device is set to off - CLIMATE_MODE_HEAT_COOL = 1, ///< Climate device is set to auto (heat/cool) - CLIMATE_MODE_COOL = 2, ///< Climate device is set to cool only - CLIMATE_MODE_HEAT = 3, ///< Climate device is set to heat only - CLIMATE_MODE_FAN_ONLY = 4, ///< Climate device is set to fan only - CLIMATE_MODE_DRY = 5, ///< Climate device is set to dry/dehumidify - CLIMATE_MODE_AUTO = 6 ///< Climate device is set to automatic mode - }; +/** + * @enum ClimateMode + * @brief Mode the climate device is set to. + * + * Represents the operating mode selected for the climate device. + * Values match ESPHome's climate component definitions. + */ +enum ClimateMode : uint8_t { + CLIMATE_MODE_OFF = 0, ///< Climate device is set to off + CLIMATE_MODE_HEAT_COOL = 1, ///< Climate device is set to auto (heat/cool) + CLIMATE_MODE_COOL = 2, ///< Climate device is set to cool only + CLIMATE_MODE_HEAT = 3, ///< Climate device is set to heat only + CLIMATE_MODE_FAN_ONLY = 4, ///< Climate device is set to fan only + CLIMATE_MODE_DRY = 5, ///< Climate device is set to dry/dehumidify + CLIMATE_MODE_AUTO = 6 ///< Climate device is set to automatic mode +}; - /** - * @enum ClimateChipVisibility - * @brief Controls when the climate chip is shown on the home screen. - * - * Sent by the blueprint on boot via `action_component_val` with `page: "mem"` - * and stored in the `climate_chip_visibility` global. - */ - enum ClimateChipVisibility : uint8_t { - CLIMATE_CHIP_ACTIVE_ONLY = 0, ///< Show chip only when actively heating/cooling/drying/fan - CLIMATE_CHIP_ALWAYS = 1, ///< Always show chip when a climate entity is configured - CLIMATE_CHIP_NEVER = 2 ///< Never show the chip, regardless of climate state - }; +/** + * @enum ClimateChipVisibility + * @brief Controls when the climate chip is shown on the home screen. + * + * Sent by the blueprint on boot via `action_component_val` with `page: "mem"` + * and stored in the `climate_chip_visibility` global. + */ +enum ClimateChipVisibility : uint8_t { + CLIMATE_CHIP_ACTIVE_ONLY = 0, ///< Show chip only when actively heating/cooling/drying/fan + CLIMATE_CHIP_ALWAYS = 1, ///< Always show chip when a climate entity is configured + CLIMATE_CHIP_NEVER = 2 ///< Never show the chip, regardless of climate state +}; - // ============================================================================= - // Climate Icon Lookup Tables - // ============================================================================= +// ============================================================================= +// Climate Icon Lookup Tables +// ============================================================================= - /** - * @brief Lookup table for CLIMATE_ACTION_OFF with different modes. - * - * Array indices correspond to ClimateMode enum values (0-6): - * - [0] CLIMATE_MODE_OFF - * - [1] CLIMATE_MODE_HEAT_COOL - * - [2] CLIMATE_MODE_COOL - * - [3] CLIMATE_MODE_HEAT - * - [4] CLIMATE_MODE_FAN_ONLY - * - [5] CLIMATE_MODE_DRY - * - [6] CLIMATE_MODE_AUTO - * - * When the climate device is OFF, the icon reflects the selected mode - * but uses grey color to indicate inactive state. - */ - constexpr IconData climate_off_mode_icons[] = { - {Icons::NONE, Colors::BLACK}, // CLIMATE_MODE_OFF (0) - {Icons::AUTORENEW, Colors::GRAY}, // CLIMATE_MODE_HEAT_COOL (1) - {Icons::SNOWFLAKE, Colors::GRAY}, // CLIMATE_MODE_COOL (2) - {Icons::FIRE, Colors::GRAY}, // CLIMATE_MODE_HEAT (3) - {Icons::FAN, Colors::GRAY}, // CLIMATE_MODE_FAN_ONLY (4) - {Icons::WATER_PERCENT, Colors::GRAY}, // CLIMATE_MODE_DRY (5) - {Icons::CALENDAR_SYNC, Colors::GRAY} // CLIMATE_MODE_AUTO (6) - }; +/** + * @brief Lookup table for CLIMATE_ACTION_OFF with different modes. + * + * Array indices correspond to ClimateMode enum values (0-6): + * - [0] CLIMATE_MODE_OFF + * - [1] CLIMATE_MODE_HEAT_COOL + * - [2] CLIMATE_MODE_COOL + * - [3] CLIMATE_MODE_HEAT + * - [4] CLIMATE_MODE_FAN_ONLY + * - [5] CLIMATE_MODE_DRY + * - [6] CLIMATE_MODE_AUTO + * + * When the climate device is OFF, the icon reflects the selected mode + * but uses grey color to indicate inactive state. + */ +constexpr IconData climate_off_mode_icons[] = { + {Icons::NONE, Colors::BLACK}, // CLIMATE_MODE_OFF (0) + {Icons::AUTORENEW, Colors::GRAY}, // CLIMATE_MODE_HEAT_COOL (1) + {Icons::SNOWFLAKE, Colors::GRAY}, // CLIMATE_MODE_COOL (2) + {Icons::FIRE, Colors::GRAY}, // CLIMATE_MODE_HEAT (3) + {Icons::FAN, Colors::GRAY}, // CLIMATE_MODE_FAN_ONLY (4) + {Icons::WATER_PERCENT, Colors::GRAY}, // CLIMATE_MODE_DRY (5) + {Icons::CALENDAR_SYNC, Colors::GRAY} // CLIMATE_MODE_AUTO (6) +}; - /** - * @brief Lookup table for active climate action states. - * - * Array indices correspond to ClimateAction enum values: - * - [0-1] Unused (reserved) - * - [2] CLIMATE_ACTION_COOLING - * - [3] CLIMATE_ACTION_HEATING - * - [4] CLIMATE_ACTION_IDLE - * - [5] CLIMATE_ACTION_DRYING - * - [6] CLIMATE_ACTION_FAN - * - * When the climate device is actively operating, the icon and color - * reflect the current action being performed. - * - * @note Indices 0-1 are unused to maintain alignment with the - * ClimateAction enum values. - */ - constexpr IconData climate_action_icons[] = { - {Icons::NONE, Colors::BLACK}, // Unused index 0 - {Icons::NONE, Colors::BLACK}, // Unused index 1 - {Icons::SNOWFLAKE, Colors::BLUE}, // CLIMATE_ACTION_COOLING (2) - {Icons::FIRE, Colors::DEEP_ORANGE}, // CLIMATE_ACTION_HEATING (3) - {Icons::THERMOMETER, Colors::GRAY}, // CLIMATE_ACTION_IDLE (4) - {Icons::WATER_PERCENT, Colors::ORANGE}, // CLIMATE_ACTION_DRYING (5) - {Icons::FAN, Colors::CYAN} // CLIMATE_ACTION_FAN (6) - }; +/** + * @brief Lookup table for active climate action states. + * + * Array indices correspond to ClimateAction enum values: + * - [0-1] Unused (reserved) + * - [2] CLIMATE_ACTION_COOLING + * - [3] CLIMATE_ACTION_HEATING + * - [4] CLIMATE_ACTION_IDLE + * - [5] CLIMATE_ACTION_DRYING + * - [6] CLIMATE_ACTION_FAN + * + * When the climate device is actively operating, the icon and color + * reflect the current action being performed. + * + * @note Indices 0-1 are unused to maintain alignment with the + * ClimateAction enum values. + */ +constexpr IconData climate_action_icons[] = { + {Icons::NONE, Colors::BLACK}, // Unused index 0 + {Icons::NONE, Colors::BLACK}, // Unused index 1 + {Icons::SNOWFLAKE, Colors::BLUE}, // CLIMATE_ACTION_COOLING (2) + {Icons::FIRE, Colors::DEEP_ORANGE}, // CLIMATE_ACTION_HEATING (3) + {Icons::THERMOMETER, Colors::GRAY}, // CLIMATE_ACTION_IDLE (4) + {Icons::WATER_PERCENT, Colors::ORANGE}, // CLIMATE_ACTION_DRYING (5) + {Icons::FAN, Colors::CYAN} // CLIMATE_ACTION_FAN (6) +}; } // namespace nspanel_easy } // namespace esphome -#endif //NSPANEL_EASY_ADDON_CLIMATE_BASE +#endif // NSPANEL_EASY_ADDON_CLIMATE_BASE diff --git a/components/nspanel_easy/addon_upload_tft.cpp b/components/nspanel_easy/addon_upload_tft.cpp index 8510d808..fa681115 100644 --- a/components/nspanel_easy/addon_upload_tft.cpp +++ b/components/nspanel_easy/addon_upload_tft.cpp @@ -7,11 +7,11 @@ namespace esphome { namespace nspanel_easy { - // TFT upload state variables (previously YAML globals) - uint8_t tft_upload_attempt = 0; - bool tft_upload_manual_request = false; - uint32_t tft_upload_first_time_synced_ms = 0; - bool tft_upload_result = false; +// TFT upload state variables (previously YAML globals) +uint8_t tft_upload_attempt = 0; +bool tft_upload_manual_request = false; +uint32_t tft_upload_first_time_synced_ms = 0; +bool tft_upload_result = false; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/addon_upload_tft.h b/components/nspanel_easy/addon_upload_tft.h index a38749e9..a73f82c5 100644 --- a/components/nspanel_easy/addon_upload_tft.h +++ b/components/nspanel_easy/addon_upload_tft.h @@ -9,11 +9,11 @@ namespace esphome { namespace nspanel_easy { - // TFT upload state variables (previously YAML globals) - extern uint8_t tft_upload_attempt; - extern bool tft_upload_manual_request; - extern uint32_t tft_upload_first_time_synced_ms; - extern bool tft_upload_result; +// TFT upload state variables (previously YAML globals) +extern uint8_t tft_upload_attempt; +extern bool tft_upload_manual_request; +extern uint32_t tft_upload_first_time_synced_ms; +extern bool tft_upload_result; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/base.cpp b/components/nspanel_easy/base.cpp index 9db43ce5..b56cb2df 100644 --- a/components/nspanel_easy/base.cpp +++ b/components/nspanel_easy/base.cpp @@ -5,39 +5,39 @@ namespace esphome { namespace nspanel_easy { - static const char *TAG_COMPONENT_BASE = "nspanel.component.base"; +static const char *TAG_COMPONENT_BASE = "nspanel.component.base"; - // Define the global system flags variable (starts with all flags false via default constructor) - SystemFlags system_flags{}; +// Define the global system flags variable (starts with all flags false via default constructor) +SystemFlags system_flags{}; - // Define the global blueprint status flags variable (starts with all flags false via default constructor) - BlueprintStatusFlags blueprint_status_flags{}; +// Define the global blueprint status flags variable (starts with all flags false via default constructor) +BlueprintStatusFlags blueprint_status_flags{}; - // Cached device name to avoid repeated lookups and string copies - std::string cached_device_name; +// Cached device name to avoid repeated lookups and string copies +std::string cached_device_name; - // Fire a Home Assistant event for NSPanel HA Blueprint - void fire_ha_event(const std::string &type, std::map data) { - // Add device name and type to the event data - data["device_name"] = cached_device_name; - data["type"] = type; +// Fire a Home Assistant event for NSPanel HA Blueprint +void fire_ha_event(const std::string &type, std::map data) { + // Add device name and type to the event data + data["device_name"] = cached_device_name; + data["type"] = type; - // Log the event being fired - ESP_LOGD(TAG_COMPONENT_BASE, "Firing HA event: %s", type.c_str()); + // Log the event being fired + ESP_LOGD(TAG_COMPONENT_BASE, "Firing HA event: %s", type.c_str()); - // Log additional data if verbose logging is enabled - #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - for (const auto &[key, value] : data) { - ESP_LOGVV(TAG_COMPONENT_BASE, " Event data: %s=%s", key.c_str(), value.c_str()); - } - #endif +// Log additional data if verbose logging is enabled +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + for (const auto &[key, value] : data) { + ESP_LOGVV(TAG_COMPONENT_BASE, " Event data: %s=%s", key.c_str(), value.c_str()); + } +#endif - // Create API device and fire the event - esphome::api::CustomAPIDevice ha_event; - ha_event.fire_homeassistant_event("esphome.nspanel_easy", data); + // Create API device and fire the event + esphome::api::CustomAPIDevice ha_event; + ha_event.fire_homeassistant_event("esphome.nspanel_easy", data); - ESP_LOGV(TAG_COMPONENT_BASE, "HA event 'esphome.nspanel_easy' sent successfully"); - } + ESP_LOGV(TAG_COMPONENT_BASE, "HA event 'esphome.nspanel_easy' sent successfully"); +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/base.h b/components/nspanel_easy/base.h index 7f015f5a..7f763479 100644 --- a/components/nspanel_easy/base.h +++ b/components/nspanel_easy/base.h @@ -2,8 +2,8 @@ #pragma once #include "esphome/components/api/custom_api_device.h" // For API HA event call -#include "esphome/core/application.h" // For App -#include "esphome/core/hal.h" // For delay() +#include "esphome/core/application.h" // For App +#include "esphome/core/hal.h" // For delay() #include "esphome/core/log.h" #include #include @@ -12,194 +12,207 @@ namespace esphome { namespace nspanel_easy { - /** - * @brief System-wide flags for NSPanel HA Blueprint component state tracking - * - * Uses bitfields to pack 16 boolean states into 2 bytes (uint16_t). - * Each flag represents a specific component state throughout the system lifecycle. - * Memory efficient: 16 individual bools would use 16 bytes; this uses only 2 bytes. - */ - struct SystemFlags { - // Boot/initialization flags (bits 0-8) - uint16_t wifi_ready : 1; ///< WiFi connection established - uint16_t api_ready : 1; ///< Home Assistant API connection ready - uint16_t baud_rate_set : 1; ///< UART baud rate configured - uint16_t nextion_ready : 1; ///< Nextion display communication ready - uint16_t blueprint_ready : 1; ///< Blueprint component initialized - uint16_t tft_ready : 1; ///< TFT display ready - uint16_t boot_completed : 1; ///< Boot script completed - uint16_t version_check_ok : 1; ///< All component versions verified - uint16_t display_settings_received : 1; ///< All display settings received - - // Runtime operation flags (bits 9-12) - uint16_t tft_upload_active : 1; ///< TFT firmware upload in progress - uint16_t safe_mode_active : 1; ///< System running in safe mode - uint16_t ota_in_progress : 1; ///< Over-the-air update active - uint16_t display_sleep : 1; ///< Display is in sleep mode - - // Reserved flags (bits 13-15) - uint16_t reserved : 3; ///< Reserved for future expansion - - // Default constructor - all flags start as false (zero-initialized) - SystemFlags() : wifi_ready(0), api_ready(0), baud_rate_set(0), nextion_ready(0), - blueprint_ready(0), tft_ready(0), boot_completed(0), version_check_ok(0), - display_settings_received(0), tft_upload_active(0), safe_mode_active(0), - ota_in_progress(0), display_sleep(0), reserved(0) {} - }; - - /** - * @brief Blueprint status flags for settings step tracking - * - * Uses bitfields to pack 8 boolean states into 1 byte (uint8_t). - * These flags track specific blueprint initialization steps. - * Bits 0-5 are active flags used in percentage calculation. - * Bits 6-7 are reserved and not used in calculations. - */ - struct BlueprintStatusFlags { - uint8_t page_home : 1; ///< Home page initialization completed - uint8_t page_qrcode : 1; ///< QR code configuration completed - uint8_t relay_settings : 1; ///< Relay settings configuration completed - uint8_t version : 1; ///< Blueprint version received - uint8_t hw_buttons_settings : 1; ///< Hardware buttons settings completed - uint8_t page_utilities : 1; ///< Utilities page configuration completed - uint8_t reserved : 2; ///< Reserved (not used in percentage calculation) - - // Default constructor - all flags start as false (zero-initialized) - BlueprintStatusFlags() : page_home(0), page_qrcode(0), relay_settings(0), - version(0), hw_buttons_settings(0), page_utilities(0), - reserved(0) {} - - /** - * `@brief` Check if all active flags (bits 0-5) are set - * `@return` true if all active flags (bits 0-5) are set, false otherwise - */ - bool all_active_flags_set() const { - // All 6 active flags must be set - return page_home && page_qrcode && relay_settings && version && hw_buttons_settings && page_utilities; - } - - /** - * `@brief` Count active flags (bits 0-5) set - * `@return` Number of flags set - */ - uint8_t count_active_flags_set() const { - return page_home + page_qrcode + relay_settings + version + hw_buttons_settings + page_utilities; - } - - /** - * `@brief` Calculate percentage of active flags that are set - * `@return` Percentage (0.0-100.0) of active flags set (bits 0-5) - */ - float get_completion_percentage() const { - static constexpr uint8_t TOTAL_ACTIVE_FLAGS = 6; - return (static_cast(count_active_flags_set()) / TOTAL_ACTIVE_FLAGS) * 100.0f; - } - - /** - * @brief Reset all blueprint status flags to their initial state (false) - * - * Clears all active flags (bits 0-5) and reserved bits, returning the - * struct to its default-constructed state. - */ - void reset() { - page_home = false; - page_qrcode = false; - relay_settings = false; - version = false; - hw_buttons_settings = false; - page_utilities = false; - reserved = 0; - } - }; - - // Global system flags - initialized to all flags false - extern SystemFlags system_flags; - - // Global blueprint status flags - initialized to all flags false - extern BlueprintStatusFlags blueprint_status_flags; - - /** - * @brief Check if device is ready to accept new tasks - * @return true if device is ready (no blocking operations active), false otherwise - */ - inline bool is_device_ready_for_tasks() { - return - system_flags.boot_completed && // Boot flag must be set to consider the system ready - // Device is NOT ready if any of these blocking operations are active - !system_flags.ota_in_progress && - !system_flags.tft_upload_active && - !system_flags.safe_mode_active; - } - - /** - * @brief Check if all non-reserved blueprint status flags are set - * @return true if all active flags (bits 1-5) are set, false otherwise - */ - inline bool is_blueprint_fully_ready() { - bool fully_ready = blueprint_status_flags.all_active_flags_set(); - - // Update system flag only if it changed - if (fully_ready != system_flags.blueprint_ready) { - system_flags.blueprint_ready = fully_ready; - } - - return fully_ready; - } - - /// @brief Feed the watchdog timer with an optional delay - /// - /// This utility function combines a delay with a watchdog timer feed operation. - /// It's commonly used in long-running operations to prevent watchdog timeouts - /// while giving the system time to process other tasks. - /// - /// @param ms Delay duration in milliseconds before feeding the watchdog (default: 5ms) - /// - /// @note This function is blocking - execution will pause for the specified duration - /// @note Minimum recommended delay is 5ms to allow system task processing - /// - /// @code - /// // Example usage with default 5ms delay - /// feed_wdt_delay(); - /// - /// // Example usage with custom 25ms delay - /// feed_wdt_delay(25); - /// - /// // Common pattern in loops - /// for (uint8_t i = 0; i < 10; ++i) { - /// // ... do work ... - /// feed_wdt_delay(5); // Prevent watchdog timeout - /// } - /// @endcode - /// - /// @see delay() - /// @see App.feed_wdt() - inline void feed_wdt_delay(uint32_t ms = 5) { - esphome::delay(ms); // Block execution for specified milliseconds - esphome::App.feed_wdt(); // Reset the watchdog timer - } - - // Cached device name to avoid repeated lookups and string copies - extern std::string cached_device_name; - - /** - * @brief Fire a Home Assistant event for NSPanel HA Blueprint - * - * Automatically adds device_name and type to the event data. - * - * @param type Event type (e.g., "button_click", "page_changed", "boot") - * @param data Additional event data (device_name and type added automatically) - * - * @note The event name is automatically set to "esphome.nspanel_easy" - * @note Call init_device_name_cache() during boot before using this function - * - * @code - * fire_ha_event("button_click", { - * {"page", "home"}, - * {"component", "bt_left"} - * }); - * @endcode - */ - void fire_ha_event(const std::string &type, std::map data = {}); +/** + * @brief System-wide flags for NSPanel HA Blueprint component state tracking + * + * Uses bitfields to pack 16 boolean states into 2 bytes (uint16_t). + * Each flag represents a specific component state throughout the system lifecycle. + * Memory efficient: 16 individual bools would use 16 bytes; this uses only 2 bytes. + */ +struct SystemFlags { + // Boot/initialization flags (bits 0-8) + uint16_t wifi_ready : 1; ///< WiFi connection established + uint16_t api_ready : 1; ///< Home Assistant API connection ready + uint16_t baud_rate_set : 1; ///< UART baud rate configured + uint16_t nextion_ready : 1; ///< Nextion display communication ready + uint16_t blueprint_ready : 1; ///< Blueprint component initialized + uint16_t tft_ready : 1; ///< TFT display ready + uint16_t boot_completed : 1; ///< Boot script completed + uint16_t version_check_ok : 1; ///< All component versions verified + uint16_t display_settings_received : 1; ///< All display settings received + + // Runtime operation flags (bits 9-12) + uint16_t tft_upload_active : 1; ///< TFT firmware upload in progress + uint16_t safe_mode_active : 1; ///< System running in safe mode + uint16_t ota_in_progress : 1; ///< Over-the-air update active + uint16_t display_sleep : 1; ///< Display is in sleep mode + + // Reserved flags (bits 13-15) + uint16_t reserved : 3; ///< Reserved for future expansion + + // Default constructor - all flags start as false (zero-initialized) + SystemFlags() + : wifi_ready(0), + api_ready(0), + baud_rate_set(0), + nextion_ready(0), + blueprint_ready(0), + tft_ready(0), + boot_completed(0), + version_check_ok(0), + display_settings_received(0), + tft_upload_active(0), + safe_mode_active(0), + ota_in_progress(0), + display_sleep(0), + reserved(0) {} +}; + +/** + * @brief Blueprint status flags for settings step tracking + * + * Uses bitfields to pack 8 boolean states into 1 byte (uint8_t). + * These flags track specific blueprint initialization steps. + * Bits 0-5 are active flags used in percentage calculation. + * Bits 6-7 are reserved and not used in calculations. + */ +struct BlueprintStatusFlags { + uint8_t page_home : 1; ///< Home page initialization completed + uint8_t page_qrcode : 1; ///< QR code configuration completed + uint8_t relay_settings : 1; ///< Relay settings configuration completed + uint8_t version : 1; ///< Blueprint version received + uint8_t hw_buttons_settings : 1; ///< Hardware buttons settings completed + uint8_t page_utilities : 1; ///< Utilities page configuration completed + uint8_t reserved : 2; ///< Reserved (not used in percentage calculation) + + // Default constructor - all flags start as false (zero-initialized) + BlueprintStatusFlags() + : page_home(0), + page_qrcode(0), + relay_settings(0), + version(0), + hw_buttons_settings(0), + page_utilities(0), + reserved(0) {} + + /** + * `@brief` Check if all active flags (bits 0-5) are set + * `@return` true if all active flags (bits 0-5) are set, false otherwise + */ + bool all_active_flags_set() const { + // All 6 active flags must be set + return page_home && page_qrcode && relay_settings && version && hw_buttons_settings && page_utilities; + } + + /** + * `@brief` Count active flags (bits 0-5) set + * `@return` Number of flags set + */ + uint8_t count_active_flags_set() const { + return page_home + page_qrcode + relay_settings + version + hw_buttons_settings + page_utilities; + } + + /** + * `@brief` Calculate percentage of active flags that are set + * `@return` Percentage (0.0-100.0) of active flags set (bits 0-5) + */ + float get_completion_percentage() const { + static constexpr uint8_t TOTAL_ACTIVE_FLAGS = 6; + return (static_cast(count_active_flags_set()) / TOTAL_ACTIVE_FLAGS) * 100.0f; + } + + /** + * @brief Reset all blueprint status flags to their initial state (false) + * + * Clears all active flags (bits 0-5) and reserved bits, returning the + * struct to its default-constructed state. + */ + void reset() { + page_home = false; + page_qrcode = false; + relay_settings = false; + version = false; + hw_buttons_settings = false; + page_utilities = false; + reserved = 0; + } +}; + +// Global system flags - initialized to all flags false +extern SystemFlags system_flags; + +// Global blueprint status flags - initialized to all flags false +extern BlueprintStatusFlags blueprint_status_flags; + +/** + * @brief Check if device is ready to accept new tasks + * @return true if device is ready (no blocking operations active), false otherwise + */ +inline bool is_device_ready_for_tasks() { + return system_flags.boot_completed && // Boot flag must be set to consider the system ready + // Device is NOT ready if any of these blocking operations are active + !system_flags.ota_in_progress && !system_flags.tft_upload_active && !system_flags.safe_mode_active; +} + +/** + * @brief Check if all non-reserved blueprint status flags are set + * @return true if all active flags (bits 1-5) are set, false otherwise + */ +inline bool is_blueprint_fully_ready() { + bool fully_ready = blueprint_status_flags.all_active_flags_set(); + + // Update system flag only if it changed + if (fully_ready != system_flags.blueprint_ready) { + system_flags.blueprint_ready = fully_ready; + } + + return fully_ready; +} + +/// @brief Feed the watchdog timer with an optional delay +/// +/// This utility function combines a delay with a watchdog timer feed operation. +/// It's commonly used in long-running operations to prevent watchdog timeouts +/// while giving the system time to process other tasks. +/// +/// @param ms Delay duration in milliseconds before feeding the watchdog (default: 5ms) +/// +/// @note This function is blocking - execution will pause for the specified duration +/// @note Minimum recommended delay is 5ms to allow system task processing +/// +/// @code +/// // Example usage with default 5ms delay +/// feed_wdt_delay(); +/// +/// // Example usage with custom 25ms delay +/// feed_wdt_delay(25); +/// +/// // Common pattern in loops +/// for (uint8_t i = 0; i < 10; ++i) { +/// // ... do work ... +/// feed_wdt_delay(5); // Prevent watchdog timeout +/// } +/// @endcode +/// +/// @see delay() +/// @see App.feed_wdt() +inline void feed_wdt_delay(uint32_t ms = 5) { + esphome::delay(ms); // Block execution for specified milliseconds + esphome::App.feed_wdt(); // Reset the watchdog timer +} + +// Cached device name to avoid repeated lookups and string copies +extern std::string cached_device_name; + +/** + * @brief Fire a Home Assistant event for NSPanel HA Blueprint + * + * Automatically adds device_name and type to the event data. + * + * @param type Event type (e.g., "button_click", "page_changed", "boot") + * @param data Additional event data (device_name and type added automatically) + * + * @note The event name is automatically set to "esphome.nspanel_easy" + * @note Call init_device_name_cache() during boot before using this function + * + * @code + * fire_ha_event("button_click", { + * {"page", "home"}, + * {"component", "bt_left"} + * }); + * @endcode + */ +void fire_ha_event(const std::string &type, std::map data = {}); } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/ha_components.cpp b/components/nspanel_easy/ha_components.cpp index 86ebca82..1d833abc 100644 --- a/components/nspanel_easy/ha_components.cpp +++ b/components/nspanel_easy/ha_components.cpp @@ -5,25 +5,25 @@ namespace esphome { namespace nspanel_easy { - // Function Definition - HomeAssistantEntity extractHomeAssistantEntity(const std::string& entity_id) { - size_t dotPos = entity_id.find("."); - HomeAssistantEntity result; - - if (dotPos != std::string::npos && dotPos > 0 && dotPos + 1 < entity_id.size()) { - // Extract domain and id from the entity_id string - result.domain = entity_id.substr(0, dotPos); - result.id = entity_id.substr(dotPos + 1); - if (result.domain == "alarm_control_panel") { - result.domain = "alarm"; - } - } else { - // No dot found, the entire entity_id is considered as id. - result.domain = "invalid"; - result.id = entity_id; - } - return result; +// Function Definition +HomeAssistantEntity extractHomeAssistantEntity(const std::string &entity_id) { + size_t dotPos = entity_id.find("."); + HomeAssistantEntity result; + + if (dotPos != std::string::npos && dotPos > 0 && dotPos + 1 < entity_id.size()) { + // Extract domain and id from the entity_id string + result.domain = entity_id.substr(0, dotPos); + result.id = entity_id.substr(dotPos + 1); + if (result.domain == "alarm_control_panel") { + result.domain = "alarm"; } + } else { + // No dot found, the entire entity_id is considered as id. + result.domain = "invalid"; + result.id = entity_id; + } + return result; +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/ha_components.h b/components/nspanel_easy/ha_components.h index e189cd33..71f4933f 100644 --- a/components/nspanel_easy/ha_components.h +++ b/components/nspanel_easy/ha_components.h @@ -8,31 +8,31 @@ namespace esphome { namespace nspanel_easy { - // Represents a Home Assistant entity with a domain and an identifier. - struct HomeAssistantEntity { - std::string domain; // The domain part of the entity, like "light" or "switch". - std::string id; // The unique identifier of the entity within its domain. - }; +// Represents a Home Assistant entity with a domain and an identifier. +struct HomeAssistantEntity { + std::string domain; // The domain part of the entity, like "light" or "switch". + std::string id; // The unique identifier of the entity within its domain. +}; - /** - * Extracts the domain name and unique ID from a given Home Assistant entity string. - * - * This function parses a Home Assistant entity string to extract and separate the domain - * and ID components of the entity. If the string does not contain a valid entity format - * (i.e., "domain.id"), the function will mark the domain as "invalid" and treat the entire - * string as the ID. It also handles a special case where "alarm_control_panel" should be - * shortened to "alarm", though the implementation of this feature needs to be added in the - * function's definition in the corresponding .cpp file. - * - * Usage example: - * auto entity = extractHomeAssistantEntity("light.kitchen"); - * // entity.domain would be "light" - * // entity.id would be "kitchen" - * - * @param entity_id The input string containing either the combined domain and unique ID or just the unique ID. - * @return A HomeAssistantEntity struct containing the extracted domain and the unique ID. - */ - HomeAssistantEntity extractHomeAssistantEntity(const std::string& entity_id); +/** + * Extracts the domain name and unique ID from a given Home Assistant entity string. + * + * This function parses a Home Assistant entity string to extract and separate the domain + * and ID components of the entity. If the string does not contain a valid entity format + * (i.e., "domain.id"), the function will mark the domain as "invalid" and treat the entire + * string as the ID. It also handles a special case where "alarm_control_panel" should be + * shortened to "alarm", though the implementation of this feature needs to be added in the + * function's definition in the corresponding .cpp file. + * + * Usage example: + * auto entity = extractHomeAssistantEntity("light.kitchen"); + * // entity.domain would be "light" + * // entity.id would be "kitchen" + * + * @param entity_id The input string containing either the combined domain and unique ID or just the unique ID. + * @return A HomeAssistantEntity struct containing the extracted domain and the unique ID. + */ +HomeAssistantEntity extractHomeAssistantEntity(const std::string &entity_id); } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/hardware.h b/components/nspanel_easy/hardware.h index a03bf52a..95063e0a 100644 --- a/components/nspanel_easy/hardware.h +++ b/components/nspanel_easy/hardware.h @@ -7,93 +7,91 @@ namespace esphome { namespace nspanel_easy { - /** - * @brief Combined hardware settings using bitfields - * - * Packs BOTH button and relay settings into a single byte (uint8_t). - * Bits 0-3: Button settings (left enabled, left state, right enabled, right state) - * Bits 4-7: Relay settings (relay1 local, relay1 fallback, relay2 local, relay2 fallback) - * - * Memory efficient: Saves 1 byte compared to separate button/relay bytes. - * Previous: 2 bytes (1 for buttons + 1 for relays) - * Now: 1 byte (contains both) - */ - struct HardwareSettings { - // Button settings (bits 0-3) - uint8_t button_left_enabled : 1; ///< Bit 0: Left button visualization enabled - uint8_t button_left_state : 1; ///< Bit 1: Left button state (0=off, 1=on) - uint8_t button_right_enabled : 1; ///< Bit 2: Right button visualization enabled - uint8_t button_right_state : 1; ///< Bit 3: Right button state (0=off, 1=on) +/** + * @brief Combined hardware settings using bitfields + * + * Packs BOTH button and relay settings into a single byte (uint8_t). + * Bits 0-3: Button settings (left enabled, left state, right enabled, right state) + * Bits 4-7: Relay settings (relay1 local, relay1 fallback, relay2 local, relay2 fallback) + * + * Memory efficient: Saves 1 byte compared to separate button/relay bytes. + * Previous: 2 bytes (1 for buttons + 1 for relays) + * Now: 1 byte (contains both) + */ +struct HardwareSettings { + // Button settings (bits 0-3) + uint8_t button_left_enabled : 1; ///< Bit 0: Left button visualization enabled + uint8_t button_left_state : 1; ///< Bit 1: Left button state (0=off, 1=on) + uint8_t button_right_enabled : 1; ///< Bit 2: Right button visualization enabled + uint8_t button_right_state : 1; ///< Bit 3: Right button state (0=off, 1=on) - // Relay settings (bits 4-7) - uint8_t relay1_local : 1; ///< Bit 4: Relay 1 local control enabled - uint8_t relay1_fallback : 1; ///< Bit 5: Relay 1 fallback mode enabled - uint8_t relay2_local : 1; ///< Bit 6: Relay 2 local control enabled - uint8_t relay2_fallback : 1; ///< Bit 7: Relay 2 fallback mode enabled + // Relay settings (bits 4-7) + uint8_t relay1_local : 1; ///< Bit 4: Relay 1 local control enabled + uint8_t relay1_fallback : 1; ///< Bit 5: Relay 1 fallback mode enabled + uint8_t relay2_local : 1; ///< Bit 6: Relay 2 local control enabled + uint8_t relay2_fallback : 1; ///< Bit 7: Relay 2 fallback mode enabled - // Default constructor - all flags start as false (zero-initialized) - HardwareSettings() : button_left_enabled(0), button_left_state(0), - button_right_enabled(0), button_right_state(0), - relay1_local(0), relay1_fallback(0), - relay2_local(0), relay2_fallback(0) {} - }; + // Default constructor - all flags start as false (zero-initialized) + HardwareSettings() + : button_left_enabled(0), + button_left_state(0), + button_right_enabled(0), + button_right_state(0), + relay1_local(0), + relay1_fallback(0), + relay2_local(0), + relay2_fallback(0) {} +}; - static_assert(sizeof(HardwareSettings) == 1, "HardwareSettings must be exactly 1 byte"); +static_assert(sizeof(HardwareSettings) == 1, "HardwareSettings must be exactly 1 byte"); - // Note: hardware_settings_raw is declared as uint8_t in ESPHome YAML with restore_value: true - // Use the helper function below to access it as a HardwareSettings struct +// Note: hardware_settings_raw is declared as uint8_t in ESPHome YAML with restore_value: true +// Use the helper function below to access it as a HardwareSettings struct - /** - * @brief Convert raw uint8_t to HardwareSettings struct - * @param raw The raw byte value containing packed settings - * @return HardwareSettings struct with all bitfields populated - */ - inline HardwareSettings from_raw(uint8_t raw) { - HardwareSettings s; - s.button_left_enabled = (raw >> 0) & 1; - s.button_left_state = (raw >> 1) & 1; - s.button_right_enabled = (raw >> 2) & 1; - s.button_right_state = (raw >> 3) & 1; - s.relay1_local = (raw >> 4) & 1; - s.relay1_fallback = (raw >> 5) & 1; - s.relay2_local = (raw >> 6) & 1; - s.relay2_fallback = (raw >> 7) & 1; - return s; - } +/** + * @brief Convert raw uint8_t to HardwareSettings struct + * @param raw The raw byte value containing packed settings + * @return HardwareSettings struct with all bitfields populated + */ +inline HardwareSettings from_raw(uint8_t raw) { + HardwareSettings s; + s.button_left_enabled = (raw >> 0) & 1; + s.button_left_state = (raw >> 1) & 1; + s.button_right_enabled = (raw >> 2) & 1; + s.button_right_state = (raw >> 3) & 1; + s.relay1_local = (raw >> 4) & 1; + s.relay1_fallback = (raw >> 5) & 1; + s.relay2_local = (raw >> 6) & 1; + s.relay2_fallback = (raw >> 7) & 1; + return s; +} - /** - * @brief Convert HardwareSettings struct to raw uint8_t - * @param s The HardwareSettings struct to pack - * @return Raw byte value with all settings packed as bitfields - */ - inline uint8_t to_raw(const HardwareSettings& s) { - return (s.button_left_enabled << 0) | - (s.button_left_state << 1) | - (s.button_right_enabled << 2) | - (s.button_right_state << 3) | - (s.relay1_local << 4) | - (s.relay1_fallback << 5) | - (s.relay2_local << 6) | - (s.relay2_fallback << 7); - } +/** + * @brief Convert HardwareSettings struct to raw uint8_t + * @param s The HardwareSettings struct to pack + * @return Raw byte value with all settings packed as bitfields + */ +inline uint8_t to_raw(const HardwareSettings &s) { + return (s.button_left_enabled << 0) | (s.button_left_state << 1) | (s.button_right_enabled << 2) | + (s.button_right_state << 3) | (s.relay1_local << 4) | (s.relay1_fallback << 5) | (s.relay2_local << 6) | + (s.relay2_fallback << 7); +} - /** - * @brief Get hardware settings from the raw uint8_t - * @param raw_value The uint8_t global variable from YAML - * @return HardwareSettings struct (copy, not reference) - */ - inline HardwareSettings get_hardware_settings(uint8_t raw_value) { - return from_raw(raw_value); - } +/** + * @brief Get hardware settings from the raw uint8_t + * @param raw_value The uint8_t global variable from YAML + * @return HardwareSettings struct (copy, not reference) + */ +inline HardwareSettings get_hardware_settings(uint8_t raw_value) { return from_raw(raw_value); } - /** - * @brief Update raw value from hardware settings - * @param raw_value Reference to the uint8_t global variable from YAML - * @param settings The HardwareSettings to write - */ - inline void set_hardware_settings(uint8_t& raw_value, const HardwareSettings& settings) { - raw_value = to_raw(settings); - } +/** + * @brief Update raw value from hardware settings + * @param raw_value Reference to the uint8_t global variable from YAML + * @param settings The HardwareSettings to write + */ +inline void set_hardware_settings(uint8_t &raw_value, const HardwareSettings &settings) { + raw_value = to_raw(settings); +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/hw_display.cpp b/components/nspanel_easy/hw_display.cpp index d2e03ed4..ced16180 100644 --- a/components/nspanel_easy/hw_display.cpp +++ b/components/nspanel_easy/hw_display.cpp @@ -7,13 +7,13 @@ namespace esphome { namespace nspanel_easy { - static const char *TAG_COMPONENT_HW_DISPLAY = "nspanel.component.hw_display"; +static const char *TAG_COMPONENT_HW_DISPLAY = "nspanel.component.hw_display"; - ThemeMode current_theme = ThemeMode::DARK; ///< Active display theme +ThemeMode current_theme = ThemeMode::DARK; ///< Active display theme - uint8_t brightness_current = 100; - uint8_t display_mode = UINT8_MAX; - uint8_t display_charset = UINT8_MAX; +uint8_t brightness_current = 100; +uint8_t display_mode = UINT8_MAX; +uint8_t display_charset = UINT8_MAX; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/hw_display.h b/components/nspanel_easy/hw_display.h index ea6f38bb..8a6fca68 100644 --- a/components/nspanel_easy/hw_display.h +++ b/components/nspanel_easy/hw_display.h @@ -15,21 +15,21 @@ namespace esphome { namespace nspanel_easy { - /** - * @brief Display theme modes. - * - * Stored as a single byte (uint8_t) for use in Nextion EEPROM - * and ESPHome globals with restore_value. - */ - enum class ThemeMode : uint8_t { - DARK = 0, ///< Dark theme - light text on dark background - LIGHT = 1, ///< Light theme - dark text on light background - }; - extern ThemeMode current_theme; ///< Active display theme - - extern uint8_t brightness_current; - extern uint8_t display_mode; - extern uint8_t display_charset; +/** + * @brief Display theme modes. + * + * Stored as a single byte (uint8_t) for use in Nextion EEPROM + * and ESPHome globals with restore_value. + */ +enum class ThemeMode : uint8_t { + DARK = 0, ///< Dark theme - light text on dark background + LIGHT = 1, ///< Light theme - dark text on light background +}; +extern ThemeMode current_theme; ///< Active display theme + +extern uint8_t brightness_current; +extern uint8_t display_mode; +extern uint8_t display_charset; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/icons.h b/components/nspanel_easy/icons.h index 23cc8c79..1be844b3 100644 --- a/components/nspanel_easy/icons.h +++ b/components/nspanel_easy/icons.h @@ -21,101 +21,101 @@ namespace esphome { namespace nspanel_easy { - // ============================================================================= - // Icon Constants - // ============================================================================= +// ============================================================================= +// Icon Constants +// ============================================================================= - /** - * @namespace Icons - * @brief MDI icon Unicode characters for display visualization. - * - * These constants represent Material Design Icons (MDI) used to display - * different device states and modes on the NSPanel display. - */ - namespace Icons { - // Alarm icons - constexpr const char* SHIELD_ALERT_OUTLINE = "\uEECC"; ///< mdi:shield-alert-outline - Triggered - constexpr const char* SHIELD_OFF_OUTLINE = "\uE99B"; ///< mdi:shield-off-outline - Disarmed - constexpr const char* SHIELD_HOME_OUTLINE = "\uECCA"; ///< mdi:shield-home-outline - Armed home - constexpr const char* SHIELD_LOCK_OUTLINE = "\uECCB"; ///< mdi:shield-lock-outline - Armed away - constexpr const char* SHIELD_MOON_OUTLINE = "\uF828"; ///< mdi:shield-moon-outline - Armed night - constexpr const char* SHIELD_AIRPLANE_OUTLINE = "\uECC6"; ///< mdi:shield-airplane-outline - Armed vacation - constexpr const char* SHIELD_HALF_FULL = "\uE77F"; ///< mdi:shield-half-full - Armed custom bypass - constexpr const char* SHIELD_OUTLINE = "\uE498"; ///< mdi:shield-outline - Pending/arming +/** + * @namespace Icons + * @brief MDI icon Unicode characters for display visualization. + * + * These constants represent Material Design Icons (MDI) used to display + * different device states and modes on the NSPanel display. + */ +namespace Icons { +// Alarm icons +constexpr const char *SHIELD_ALERT_OUTLINE = "\uEECC"; ///< mdi:shield-alert-outline - Triggered +constexpr const char *SHIELD_OFF_OUTLINE = "\uE99B"; ///< mdi:shield-off-outline - Disarmed +constexpr const char *SHIELD_HOME_OUTLINE = "\uECCA"; ///< mdi:shield-home-outline - Armed home +constexpr const char *SHIELD_LOCK_OUTLINE = "\uECCB"; ///< mdi:shield-lock-outline - Armed away +constexpr const char *SHIELD_MOON_OUTLINE = "\uF828"; ///< mdi:shield-moon-outline - Armed night +constexpr const char *SHIELD_AIRPLANE_OUTLINE = "\uECC6"; ///< mdi:shield-airplane-outline - Armed vacation +constexpr const char *SHIELD_HALF_FULL = "\uE77F"; ///< mdi:shield-half-full - Armed custom bypass +constexpr const char *SHIELD_OUTLINE = "\uE498"; ///< mdi:shield-outline - Pending/arming - // Climate icons - constexpr const char* AUTORENEW = "\uE069"; ///< mdi:autorenew - Auto/heat-cool mode - constexpr const char* SNOWFLAKE = "\uE716"; ///< mdi:snowflake - Cooling mode - constexpr const char* FIRE = "\uE237"; ///< mdi:fire - Heating mode - constexpr const char* FAN = "\uE20F"; ///< mdi:fan - Fan mode - constexpr const char* WATER_PERCENT = "\uE58D"; ///< mdi:water-percent - Dry/dehumidify mode - constexpr const char* CALENDAR_SYNC = "\uEE8D"; ///< mdi:calendar-sync - Auto mode (deprecated) - constexpr const char* REFRESH_AUTO = "\uF8F1"; ///< mdi:refresh-auto - Auto mode - constexpr const char* THERMOMETER = "\uE50E"; ///< mdi:thermometer - Idle state - constexpr const char* NONE = "\uFFFF"; ///< Hidden/no icon (blank character) +// Climate icons +constexpr const char *AUTORENEW = "\uE069"; ///< mdi:autorenew - Auto/heat-cool mode +constexpr const char *SNOWFLAKE = "\uE716"; ///< mdi:snowflake - Cooling mode +constexpr const char *FIRE = "\uE237"; ///< mdi:fire - Heating mode +constexpr const char *FAN = "\uE20F"; ///< mdi:fan - Fan mode +constexpr const char *WATER_PERCENT = "\uE58D"; ///< mdi:water-percent - Dry/dehumidify mode +constexpr const char *CALENDAR_SYNC = "\uEE8D"; ///< mdi:calendar-sync - Auto mode (deprecated) +constexpr const char *REFRESH_AUTO = "\uF8F1"; ///< mdi:refresh-auto - Auto mode +constexpr const char *THERMOMETER = "\uE50E"; ///< mdi:thermometer - Idle state +constexpr const char *NONE = "\uFFFF"; ///< Hidden/no icon (blank character) - // System/WiFi icons - constexpr const char* WIFI = "\uE5A8"; ///< mdi:wifi - WiFi connected - // Renamed to avoid collision with Arduino WiFiType.h macro WIFI_OFF - constexpr const char* WIFI_DISCONNECTED = "\uE5A9"; ///< mdi:wifi-off - WiFi disconnected - // constexpr const char* WIFI_OFF = "\uE5A9"; ///< mdi:wifi-off - WiFi disconnected - constexpr const char* API_OFF = "\uF256"; ///< mdi:api-off - API disconnected - constexpr const char* HOME_ASSISTANT = "\uE7CF"; ///< mdi:home-assistant - Blueprint disconnected - constexpr const char* RESTART = "\uE708"; ///< mdi:restart - System restart - } // namespace Icons +// System/WiFi icons +constexpr const char *WIFI = "\uE5A8"; ///< mdi:wifi - WiFi connected +// Renamed to avoid collision with Arduino WiFiType.h macro WIFI_OFF +constexpr const char *WIFI_DISCONNECTED = "\uE5A9"; ///< mdi:wifi-off - WiFi disconnected +// constexpr const char* WIFI_OFF = "\uE5A9"; ///< mdi:wifi-off - WiFi disconnected +constexpr const char *API_OFF = "\uF256"; ///< mdi:api-off - API disconnected +constexpr const char *HOME_ASSISTANT = "\uE7CF"; ///< mdi:home-assistant - Blueprint disconnected +constexpr const char *RESTART = "\uE708"; ///< mdi:restart - System restart +} // namespace Icons - // ============================================================================= - // Color Constants - // ============================================================================= +// ============================================================================= +// Color Constants +// ============================================================================= - /** - * @namespace Colors - * @brief RGB565 color values for state visualization. - * - * Color constants used to indicate different device states. - * Values are in RGB565 format (16-bit color depth) compatible with - * the Nextion display hardware. - */ - namespace Colors { - constexpr uint16_t BLACK = 0; ///< Hidden/invisible (RGB565: 0x0000) - constexpr uint16_t BLUE = 1055; ///< Cooling action (RGB565: 0x041F) - constexpr uint16_t BLUE_BLUETOOTH = 1279; ///< Bluetooth blue (RGB565: 0x04FF) - constexpr uint16_t BLUE_INDIGO = 10597; ///< Indigo blue (RGB565: 0x2965) - constexpr uint16_t CYAN = 1530; ///< Fan action (RGB565: 0x05FA) - constexpr uint16_t CYAN_BRIGHT = 7519; ///< Bright cyan (RGB565: 0x1D5F) - constexpr uint16_t DEEP_ORANGE = 64164; ///< Heating action (RGB565: 0xFAA4) - constexpr uint16_t GREEN = 19818; ///< Armed/active state (RGB565: 0x4D6A) - constexpr uint16_t GRAY = 35921; ///< Inactive/off state (RGB565: 0x8C51) - constexpr uint16_t GRAY_DARK = 16904; ///< Hidden/disabled buttons - constexpr uint16_t GRAY_DARKEST = 6339; ///< Hidden/disabled buttons - constexpr uint16_t GRAY_LIGHT = 52857; ///< Gray light/silver (RGB565: 0xCE79) - constexpr uint16_t GRAY_MEDIUM = 29614; ///< Mid gray, secondary text on light bg (RGB565: 0x73AE) - constexpr uint16_t GRAY_MOSS = 33808; ///< Moss gray/green (RGB565: 0x8410) - constexpr uint16_t YELLOW_GREEN = 48631; ///< Inactive buttons - constexpr uint16_t ORANGE = 64704; ///< Drying action (RGB565: 0xFCC0) - constexpr uint16_t PURPLE_MEDIUM = 38004; ///< Medium purple (RGB565: 0x9474) - constexpr uint16_t RED = 63488; ///< Alert/triggered state (RGB565: 0xF800) - constexpr uint16_t WHITE = 65535; ///< White/default (RGB565: 0xFFFF) - constexpr uint16_t YELLOW = 65024; ///< Warning/pending state (RGB565: 0xFE00) - constexpr uint16_t YELLOW_GOLDEN = 64992; ///< Golden yellow (RGB565: 0xFDE0) - } // namespace Colors +/** + * @namespace Colors + * @brief RGB565 color values for state visualization. + * + * Color constants used to indicate different device states. + * Values are in RGB565 format (16-bit color depth) compatible with + * the Nextion display hardware. + */ +namespace Colors { +constexpr uint16_t BLACK = 0; ///< Hidden/invisible (RGB565: 0x0000) +constexpr uint16_t BLUE = 1055; ///< Cooling action (RGB565: 0x041F) +constexpr uint16_t BLUE_BLUETOOTH = 1279; ///< Bluetooth blue (RGB565: 0x04FF) +constexpr uint16_t BLUE_INDIGO = 10597; ///< Indigo blue (RGB565: 0x2965) +constexpr uint16_t CYAN = 1530; ///< Fan action (RGB565: 0x05FA) +constexpr uint16_t CYAN_BRIGHT = 7519; ///< Bright cyan (RGB565: 0x1D5F) +constexpr uint16_t DEEP_ORANGE = 64164; ///< Heating action (RGB565: 0xFAA4) +constexpr uint16_t GREEN = 19818; ///< Armed/active state (RGB565: 0x4D6A) +constexpr uint16_t GRAY = 35921; ///< Inactive/off state (RGB565: 0x8C51) +constexpr uint16_t GRAY_DARK = 16904; ///< Hidden/disabled buttons +constexpr uint16_t GRAY_DARKEST = 6339; ///< Hidden/disabled buttons +constexpr uint16_t GRAY_LIGHT = 52857; ///< Gray light/silver (RGB565: 0xCE79) +constexpr uint16_t GRAY_MEDIUM = 29614; ///< Mid gray, secondary text on light bg (RGB565: 0x73AE) +constexpr uint16_t GRAY_MOSS = 33808; ///< Moss gray/green (RGB565: 0x8410) +constexpr uint16_t YELLOW_GREEN = 48631; ///< Inactive buttons +constexpr uint16_t ORANGE = 64704; ///< Drying action (RGB565: 0xFCC0) +constexpr uint16_t PURPLE_MEDIUM = 38004; ///< Medium purple (RGB565: 0x9474) +constexpr uint16_t RED = 63488; ///< Alert/triggered state (RGB565: 0xF800) +constexpr uint16_t WHITE = 65535; ///< White/default (RGB565: 0xFFFF) +constexpr uint16_t YELLOW = 65024; ///< Warning/pending state (RGB565: 0xFE00) +constexpr uint16_t YELLOW_GOLDEN = 64992; ///< Golden yellow (RGB565: 0xFDE0) +} // namespace Colors - // ============================================================================= - // Display Data Structures - // ============================================================================= +// ============================================================================= +// Display Data Structures +// ============================================================================= - /** - * @struct IconData - * @brief Associates an icon character with its display color. - * - * This structure pairs a pointer to an icon Unicode character with - * a corresponding RGB565 color value for display on the NSPanel. - * Used for various entity types including climate, light, cover, etc. - */ - struct IconData { - const char* icon; ///< Pointer to icon Unicode character string - uint16_t color; ///< RGB565 color value for icon display - }; +/** + * @struct IconData + * @brief Associates an icon character with its display color. + * + * This structure pairs a pointer to an icon Unicode character with + * a corresponding RGB565 color value for display on the NSPanel. + * Used for various entity types including climate, light, cover, etc. + */ +struct IconData { + const char *icon; ///< Pointer to icon Unicode character string + uint16_t color; ///< RGB565 color value for icon display +}; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/macros.h b/components/nspanel_easy/macros.h index 33c61cad..0be5a91c 100644 --- a/components/nspanel_easy/macros.h +++ b/components/nspanel_easy/macros.h @@ -8,10 +8,10 @@ /** * @brief Conditional error logging macro - * + * * Logs an error message only if the condition is true and error logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes error messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -25,16 +25,16 @@ } \ } while (0) #else -#define ESP_LOGE_IF(tag, condition, ...) ((void)0) +#define ESP_LOGE_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR #endif // ESP_LOGE_IF /** * @brief Conditional warning logging macro - * + * * Logs a warning message only if the condition is true and warning logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes warning messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -48,16 +48,16 @@ } \ } while (0) #else -#define ESP_LOGW_IF(tag, condition, ...) ((void)0) +#define ESP_LOGW_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN #endif // ESP_LOGW_IF /** * @brief Conditional info logging macro - * + * * Logs an info message only if the condition is true and info logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes info messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -71,16 +71,16 @@ } \ } while (0) #else -#define ESP_LOGI_IF(tag, condition, ...) ((void)0) +#define ESP_LOGI_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO #endif // ESP_LOGI_IF /** * @brief Conditional debug logging macro - * + * * Logs a debug message only if the condition is true and debug logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes debug messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -94,16 +94,16 @@ } \ } while (0) #else -#define ESP_LOGD_IF(tag, condition, ...) ((void)0) +#define ESP_LOGD_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG #endif // ESP_LOGD_IF /** * @brief Conditional config logging macro - * + * * Logs a config message only if the condition is true and config logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes config messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -117,16 +117,16 @@ } \ } while (0) #else -#define ESP_LOGCONFIG_IF(tag, condition, ...) ((void)0) +#define ESP_LOGCONFIG_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_CONFIG #endif // ESP_LOGCONFIG_IF /** * @brief Conditional verbose logging macro - * + * * Logs a verbose message only if the condition is true and verbose logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes verbose messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -140,16 +140,16 @@ } \ } while (0) #else -#define ESP_LOGV_IF(tag, condition, ...) ((void)0) +#define ESP_LOGV_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #endif // ESP_LOGV_IF /** * @brief Conditional very verbose logging macro - * + * * Logs a very verbose message only if the condition is true and very verbose logging is enabled. * The condition is only evaluated if ESPHOME_LOG_LEVEL includes very verbose messages. - * + * * @param tag Log tag (const char*) * @param condition Boolean condition to check * @param ... Printf-style format string and arguments @@ -163,19 +163,19 @@ } \ } while (0) #else -#define ESP_LOGVV_IF(tag, condition, ...) ((void)0) +#define ESP_LOGVV_IF(tag, condition, ...) ((void) 0) #endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE #endif // ESP_LOGVV_IF // Example usage with your version comparison code: /* -ESP_LOGE_IF("${TAG_CORE}", - not compare_versions("${version}", version_tft->state.c_str()), +ESP_LOGE_IF("${TAG_CORE}", + not compare_versions("${version}", version_tft->state.c_str()), "TFT version mismatch!"); ESP_LOGD("${TAG_CORE}", " Blueprint: %s", version_blueprint->state.c_str()); -ESP_LOGE_IF("${TAG_CORE}", - not compare_versions("${version}", version_blueprint->state.c_str()), +ESP_LOGE_IF("${TAG_CORE}", + not compare_versions("${version}", version_blueprint->state.c_str()), "Blueprint version mismatch!"); */ diff --git a/components/nspanel_easy/nextion_components.cpp b/components/nspanel_easy/nextion_components.cpp index d8c1ee40..0aa43658 100644 --- a/components/nspanel_easy/nextion_components.cpp +++ b/components/nspanel_easy/nextion_components.cpp @@ -7,26 +7,26 @@ namespace esphome { namespace nspanel_easy { - NSPanelEasyNextionComponent extractNextionComponent(const std::string& input, const std::string& defaultPage) { - NSPanelEasyNextionComponent result{}; - size_t dotPos = input.find("."); +NSPanelEasyNextionComponent extractNextionComponent(const std::string &input, const std::string &defaultPage) { + NSPanelEasyNextionComponent result{}; + size_t dotPos = input.find("."); - if (dotPos != std::string::npos) { - // Handling special case and standard extraction - strncpy(result.page, input.substr(0, std::min(dotPos, 14)).c_str(), 14); - result.page[14] = '\0'; // Ensure null termination - strncpy(result.component_id, input.substr(dotPos + 1, 14).c_str(), 14); - result.component_id[14] = '\0'; // Ensure null termination - } else { - // Default page case - strncpy(result.page, defaultPage.c_str(), 14); - result.page[14] = '\0'; // Ensure null termination - strncpy(result.component_id, input.c_str(), 14); - result.component_id[14] = '\0'; // Ensure null termination - } + if (dotPos != std::string::npos) { + // Handling special case and standard extraction + strncpy(result.page, input.substr(0, std::min(dotPos, 14)).c_str(), 14); + result.page[14] = '\0'; // Ensure null termination + strncpy(result.component_id, input.substr(dotPos + 1, 14).c_str(), 14); + result.component_id[14] = '\0'; // Ensure null termination + } else { + // Default page case + strncpy(result.page, defaultPage.c_str(), 14); + result.page[14] = '\0'; // Ensure null termination + strncpy(result.component_id, input.c_str(), 14); + result.component_id[14] = '\0'; // Ensure null termination + } - return result; - } + return result; +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/nextion_components.h b/components/nspanel_easy/nextion_components.h index a0d9d04b..1e1972f2 100644 --- a/components/nspanel_easy/nextion_components.h +++ b/components/nspanel_easy/nextion_components.h @@ -9,72 +9,70 @@ namespace esphome { namespace nspanel_easy { - /** - * @struct HMIComponent - * @brief Associates an HMI component name with its ID. - * - * This structure pairs a component name (up to 14 characters) with - * a corresponding ID (0-255) for Nextion display communication. - * Designed for compile-time constant definitions with minimal memory usage. - */ - struct HMIComponent { - const char* name; ///< Component name (max 14 chars) - uint8_t id; ///< Component ID (0-255) - }; +/** + * @struct HMIComponent + * @brief Associates an HMI component name with its ID. + * + * This structure pairs a component name (up to 14 characters) with + * a corresponding ID (0-255) for Nextion display communication. + * Designed for compile-time constant definitions with minimal memory usage. + */ +struct HMIComponent { + const char *name; ///< Component name (max 14 chars) + uint8_t id; ///< Component ID (0-255) +}; - struct NSPanelEasyNextionComponent { - char page[15]; // 14 characters + null terminator, representing the Nextion display page - char component_id[15]; // 14 characters + null terminator, representing the component ID within the page - }; +struct NSPanelEasyNextionComponent { + char page[15]; // 14 characters + null terminator, representing the Nextion display page + char component_id[15]; // 14 characters + null terminator, representing the component ID within the page +}; - /** - * Extracts the page name and component ID from a given input string. - * If the input string omits the page, a default page name is used. - * Handles a special case for "alarm_control_panel" by shortening it to "alarm". - * - * @param input The input string containing the component ID, optionally prefixed by the page name and a dot. - * @param defaultPage The default page name to use if the input string does not specify a page. - * @return A NSPanelEasyNextionComponent struct with the extracted or default page name, component ID, and current page status. - */ - NSPanelEasyNextionComponent extractNextionComponent(const std::string& input, const std::string& defaultPage); +/** + * Extracts the page name and component ID from a given input string. + * If the input string omits the page, a default page name is used. + * Handles a special case for "alarm_control_panel" by shortening it to "alarm". + * + * @param input The input string containing the component ID, optionally prefixed by the page name and a dot. + * @param defaultPage The default page name to use if the input string does not specify a page. + * @return A NSPanelEasyNextionComponent struct with the extracted or default page name, component ID, and current page + * status. + */ +NSPanelEasyNextionComponent extractNextionComponent(const std::string &input, const std::string &defaultPage); - /** - * Converts an RGB color represented as a vector of integers to the 16-bit 5-6-5 format supported by Nextion displays. - * - * This function takes a vector containing three integer values representing - * the red, green, and blue components of an RGB color, each in the range 0-255. - * It then converts these values into a single uint16_t value in 5-6-5 format, - * commonly used for color displays. The conversion process masks and shifts - * the components to fit into the 5 bits for red, 6 bits for green, and 5 bits for blue. - * - * @param rgb A vector of integers with exactly three elements: [red, green, blue]. - * @return The color encoded in 16-bit 5-6-5 format, or UINT16_MAX if the input vector - * does not contain at least three elements. - */ - template - inline uint16_t rgbTo565(const Container& rgb) { - if (rgb.size() != 3) { - return UINT16_MAX; // Use UINT16_MAX as an error indicator - } - return ((rgb[0] & 0xF8) << 8) | ((rgb[1] & 0xFC) << 3) | (rgb[2] >> 3); - } +/** + * Converts an RGB color represented as a vector of integers to the 16-bit 5-6-5 format supported by Nextion displays. + * + * This function takes a vector containing three integer values representing + * the red, green, and blue components of an RGB color, each in the range 0-255. + * It then converts these values into a single uint16_t value in 5-6-5 format, + * commonly used for color displays. The conversion process masks and shifts + * the components to fit into the 5 bits for red, 6 bits for green, and 5 bits for blue. + * + * @param rgb A vector of integers with exactly three elements: [red, green, blue]. + * @return The color encoded in 16-bit 5-6-5 format, or UINT16_MAX if the input vector + * does not contain at least three elements. + */ +template inline uint16_t rgbTo565(const Container &rgb) { + if (rgb.size() != 3) { + return UINT16_MAX; // Use UINT16_MAX as an error indicator + } + return ((rgb[0] & 0xF8) << 8) | ((rgb[1] & 0xFC) << 3) | (rgb[2] >> 3); +} - /** - * @brief Computes the inverse of an RGB565 color. - * - * Inverts each channel independently: - * - Red: 5-bit inversion (max 31) - * - Green: 6-bit inversion (max 63) - * - Blue: 5-bit inversion (max 31) - * - * Equivalent to `(~color) & 0xFFFF`. - * - * @param color RGB565 color value to invert. - * @return Inverted RGB565 color value. - */ - constexpr uint16_t invertColor565(uint16_t color) { - return (~color) & 0xFFFF; - } +/** + * @brief Computes the inverse of an RGB565 color. + * + * Inverts each channel independently: + * - Red: 5-bit inversion (max 31) + * - Green: 6-bit inversion (max 63) + * - Blue: 5-bit inversion (max 31) + * + * Equivalent to `(~color) & 0xFFFF`. + * + * @param color RGB565 color value to invert. + * @return Inverted RGB565 color value. + */ +constexpr uint16_t invertColor565(uint16_t color) { return (~color) & 0xFFFF; } } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/nspanel_easy.h b/components/nspanel_easy/nspanel_easy.h index 11b430b8..bcc2f515 100644 --- a/components/nspanel_easy/nspanel_easy.h +++ b/components/nspanel_easy/nspanel_easy.h @@ -10,18 +10,14 @@ namespace nspanel_easy { /// @brief Main NSPanel Easy component — provides on_setup and on_dump_config triggers. class NSPanelEasyComponent : public Component { public: - void setup() override { - this->on_setup_callbacks_.call(); - } + void setup() override { this->on_setup_callbacks_.call(); } void dump_config() override { ESP_LOGCONFIG(TAG, "NSPanel Easy:"); this->on_dump_config_callbacks_.call(); } - void add_on_setup_callback(std::function &&callback) { - this->on_setup_callbacks_.add(std::move(callback)); - } + void add_on_setup_callback(std::function &&callback) { this->on_setup_callbacks_.add(std::move(callback)); } void add_on_dump_config_callback(std::function &&callback) { this->on_dump_config_callbacks_.add(std::move(callback)); diff --git a/components/nspanel_easy/page_alarm.h b/components/nspanel_easy/page_alarm.h index e8a9a2a7..c22c01d1 100644 --- a/components/nspanel_easy/page_alarm.h +++ b/components/nspanel_easy/page_alarm.h @@ -5,16 +5,16 @@ namespace esphome { namespace nspanel_easy { - // Home Assistant alarm control panel supported features - // Matches Home Assistant AlarmControlPanelEntityFeature values - enum class AlarmFeature : uint8_t { - ARM_HOME = 1, // 0b00000001 - ARM_AWAY = 2, // 0b00000010 - ARM_NIGHT = 4, // 0b00000100 - ARM_CUSTOM_BYPASS = 16, // 0b00010000 - ARM_VACATION = 32, // 0b00100000 - ALWAYS_SHOW = 0 // Special value for always-visible buttons - }; +// Home Assistant alarm control panel supported features +// Matches Home Assistant AlarmControlPanelEntityFeature values +enum class AlarmFeature : uint8_t { + ARM_HOME = 1, // 0b00000001 + ARM_AWAY = 2, // 0b00000010 + ARM_NIGHT = 4, // 0b00000100 + ARM_CUSTOM_BYPASS = 16, // 0b00010000 + ARM_VACATION = 32, // 0b00100000 + ALWAYS_SHOW = 0 // Special value for always-visible buttons +}; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_climate.cpp b/components/nspanel_easy/page_climate.cpp index 4a5dc880..07a1b742 100644 --- a/components/nspanel_easy/page_climate.cpp +++ b/components/nspanel_easy/page_climate.cpp @@ -7,16 +7,16 @@ namespace esphome { namespace nspanel_easy { - float set_climate_current_temp; - uint32_t set_climate_supported_features; - float set_climate_target_temp; - float set_climate_target_temp_high; - float set_climate_target_temp_low; - uint8_t set_climate_temp_step; - uint16_t set_climate_total_steps; - uint16_t set_climate_temp_offset; - std::string set_climate_climate_icon; - bool set_climate_embedded_climate; +float set_climate_current_temp; +uint32_t set_climate_supported_features; +float set_climate_target_temp; +float set_climate_target_temp_high; +float set_climate_target_temp_low; +uint8_t set_climate_temp_step; +uint16_t set_climate_total_steps; +uint16_t set_climate_temp_offset; +std::string set_climate_climate_icon; +bool set_climate_embedded_climate; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_climate.h b/components/nspanel_easy/page_climate.h index 17318534..d3db024f 100644 --- a/components/nspanel_easy/page_climate.h +++ b/components/nspanel_easy/page_climate.h @@ -10,16 +10,16 @@ namespace esphome { namespace nspanel_easy { - extern float set_climate_current_temp; - extern uint32_t set_climate_supported_features; - extern float set_climate_target_temp; - extern float set_climate_target_temp_high; - extern float set_climate_target_temp_low; - extern uint8_t set_climate_temp_step; - extern uint16_t set_climate_total_steps; - extern uint16_t set_climate_temp_offset; - extern std::string set_climate_climate_icon; - extern bool set_climate_embedded_climate; +extern float set_climate_current_temp; +extern uint32_t set_climate_supported_features; +extern float set_climate_target_temp; +extern float set_climate_target_temp_high; +extern float set_climate_target_temp_low; +extern uint8_t set_climate_temp_step; +extern uint16_t set_climate_total_steps; +extern uint16_t set_climate_temp_offset; +extern std::string set_climate_climate_icon; +extern bool set_climate_embedded_climate; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_entities.cpp b/components/nspanel_easy/page_entities.cpp index a4e4a4ab..ae34f172 100644 --- a/components/nspanel_easy/page_entities.cpp +++ b/components/nspanel_easy/page_entities.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace nspanel_easy { - uint8_t page_entities_value_horizontal_alignment = 1; // Horizontal alignment: 0-Left; 1-Center; 2-Right +uint8_t page_entities_value_horizontal_alignment = 1; // Horizontal alignment: 0-Left; 1-Center; 2-Right } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_entities.h b/components/nspanel_easy/page_entities.h index c48e052a..25ff3d4d 100644 --- a/components/nspanel_easy/page_entities.h +++ b/components/nspanel_easy/page_entities.h @@ -9,7 +9,7 @@ namespace esphome { namespace nspanel_easy { - extern uint8_t page_entities_value_horizontal_alignment; +extern uint8_t page_entities_value_horizontal_alignment; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_home.h b/components/nspanel_easy/page_home.h index 04b9949a..1dbf7c91 100644 --- a/components/nspanel_easy/page_home.h +++ b/components/nspanel_easy/page_home.h @@ -16,91 +16,91 @@ namespace esphome { namespace nspanel_easy { - namespace hmi { - namespace home { - - /** - * @namespace home - * @brief Components for the Home page. - * - * Component ID mapping for the Home page (ID: 0) - * Based on the Nextion HMI design file. - */ - - // Page definition - constexpr HMIComponent PAGE = {"home", 1}; ///< Home page - - // Time and date components - constexpr HMIComponent TIME = {"home.time", 3}; ///< Time display (5 chars max) - constexpr HMIComponent DATE = {"home.date", 6}; ///< Date display (25 chars max) - constexpr HMIComponent MERIDIEM = {"home.meridiem", 32}; ///< AM/PM indicator (6 chars max) - - // Temperature and weather components - constexpr HMIComponent INDR_TEMP = {"home.indr_temp", 4}; ///< Indoor temperature (8 chars max) - constexpr HMIComponent INDR_TEMP_ICON = {"home.indr_temp_icon", 27}; ///< Indoor temp icon (3 chars max) - constexpr HMIComponent OUTDOOR_TEMP = {"home.outdoor_temp", 5}; ///< Outdoor temperature (8 chars max) - constexpr HMIComponent WEATHER = {"home.weather", 7}; ///< Weather picture - - // Value display components - constexpr HMIComponent VALUE01 = {"home.value01", 8}; ///< Value 1 text (30 chars max) - constexpr HMIComponent VALUE01_ICON = {"home.value01_icon", 21}; ///< Value 1 icon (3 chars max) - constexpr HMIComponent VALUE02 = {"home.value02", 25}; ///< Value 2 text (30 chars max) - constexpr HMIComponent VALUE02_ICON = {"home.value02_icon", 24}; ///< Value 2 icon (3 chars max) - constexpr HMIComponent VALUE03 = {"home.value03", 45}; ///< Value 3 text (30 chars max) - constexpr HMIComponent VALUE03_ICON = {"home.value03_icon", 44}; ///< Value 3 icon (3 chars max) - constexpr HMIComponent VALUE04 = {"home.value04", 22}; ///< Value 4 text (30 chars max) - constexpr HMIComponent VALUE04_ICON = {"home.value04_icon", 23}; ///< Value 4 icon (3 chars max) - - // Chip components (top row) - constexpr HMIComponent CHIP_RELAY1 = {"home.chip_relay1", 11}; ///< Relay 1 chip (3 chars max) - constexpr HMIComponent CHIP_RELAY2 = {"home.chip_relay2", 12}; ///< Relay 2 chip (3 chars max) - constexpr HMIComponent CHIP_CLIMATE = {"home.chip_climate", 13}; ///< Climate chip (3 chars max) - constexpr HMIComponent CHIP01 = {"home.chip01", 14}; ///< Chip 1 (3 chars max) - constexpr HMIComponent CHIP02 = {"home.chip02", 15}; ///< Chip 2 (3 chars max) - constexpr HMIComponent CHIP03 = {"home.chip03", 16}; ///< Chip 3 (3 chars max) - constexpr HMIComponent CHIP04 = {"home.chip04", 17}; ///< Chip 4 (3 chars max) - constexpr HMIComponent CHIP05 = {"home.chip05", 18}; ///< Chip 5 (3 chars max) - constexpr HMIComponent CHIP06 = {"home.chip06", 19}; ///< Chip 6 (3 chars max) - constexpr HMIComponent CHIP07 = {"home.chip07", 20}; ///< Chip 7 (3 chars max) - - // Button components - constexpr HMIComponent BUTTON01 = {"home.button01", 35}; ///< Button 1 (3 chars max) - constexpr HMIComponent BUTTON02 = {"home.button02", 36}; ///< Button 2 (3 chars max) - constexpr HMIComponent BUTTON03 = {"home.button03", 37}; ///< Button 3 (3 chars max) - constexpr HMIComponent BUTTON04 = {"home.button04", 40}; ///< Button 4 (3 chars max) - constexpr HMIComponent BUTTON05 = {"home.button05", 41}; ///< Button 5 (3 chars max) - constexpr HMIComponent BUTTON06 = {"home.button06", 42}; ///< Button 6 (3 chars max) - constexpr HMIComponent BUTTON07 = {"home.button07", 31}; ///< Button 7 (3 chars max) - - // Bottom navigation buttons - constexpr HMIComponent BT_NOTIFIC = {"home.bt_notific", 28}; ///< Notifications button (3 chars max) - constexpr HMIComponent BT_QRCODE = {"home.bt_qrcode", 29}; ///< QR code button (3 chars max) - constexpr HMIComponent BT_ENTITIES = {"home.bt_entities", 30}; ///< Entities button (3 chars max) - constexpr HMIComponent BT_UTILITIES = {"home.bt_utilities", 43}; ///< Utilities button (3 chars max) - - // Footer text components - constexpr HMIComponent LEFT_BT_TEXT = {"home.left_bt_text", 9}; ///< Left button text (20 chars max) - constexpr HMIComponent RIGHT_BT_TEXT = {"home.right_bt_text", 10}; ///< Right button text (20 chars max) - - // System indicators - constexpr HMIComponent WIFI_ICON = {"home.wifi_icon", 26}; ///< WiFi status icon (5 chars max) - constexpr HMIComponent BT_ICON = {"home.bt_icon", 46}; ///< Bluetooth/system icon (5 chars max) - - // Variables (for reference - these are not visual components) - constexpr HMIComponent VAR_LASTCLICK = {"home.lastclick", 33}; ///< Last click variable - constexpr HMIComponent VAR_CLICK_COMP = {"home.click_comp", 38}; ///< Clicked component variable - - // Touch components - constexpr HMIComponent SWIPE = {"home.swipe", 1}; ///< Swipe gesture handler - - // Timers (for reference) - constexpr HMIComponent TIMER_SWIPESTORE = {"home.swipestore", 2}; ///< Swipe store timer - constexpr HMIComponent TIMER_SETTINGS = {"home.settings_timer", 34}; ///< Settings timer - constexpr HMIComponent TIMER_CLICK = {"home.click_timer", 39}; ///< Click timer - constexpr HMIComponent TIMER_SHORT_CLICK = {"home.short_click_tm", 47}; ///< Short click timer - - } // namespace home - } // namespace hmi +namespace hmi { +namespace home { + +/** + * @namespace home + * @brief Components for the Home page. + * + * Component ID mapping for the Home page (ID: 0) + * Based on the Nextion HMI design file. + */ + +// Page definition +constexpr HMIComponent PAGE = {"home", 1}; ///< Home page + +// Time and date components +constexpr HMIComponent TIME = {"home.time", 3}; ///< Time display (5 chars max) +constexpr HMIComponent DATE = {"home.date", 6}; ///< Date display (25 chars max) +constexpr HMIComponent MERIDIEM = {"home.meridiem", 32}; ///< AM/PM indicator (6 chars max) + +// Temperature and weather components +constexpr HMIComponent INDR_TEMP = {"home.indr_temp", 4}; ///< Indoor temperature (8 chars max) +constexpr HMIComponent INDR_TEMP_ICON = {"home.indr_temp_icon", 27}; ///< Indoor temp icon (3 chars max) +constexpr HMIComponent OUTDOOR_TEMP = {"home.outdoor_temp", 5}; ///< Outdoor temperature (8 chars max) +constexpr HMIComponent WEATHER = {"home.weather", 7}; ///< Weather picture + +// Value display components +constexpr HMIComponent VALUE01 = {"home.value01", 8}; ///< Value 1 text (30 chars max) +constexpr HMIComponent VALUE01_ICON = {"home.value01_icon", 21}; ///< Value 1 icon (3 chars max) +constexpr HMIComponent VALUE02 = {"home.value02", 25}; ///< Value 2 text (30 chars max) +constexpr HMIComponent VALUE02_ICON = {"home.value02_icon", 24}; ///< Value 2 icon (3 chars max) +constexpr HMIComponent VALUE03 = {"home.value03", 45}; ///< Value 3 text (30 chars max) +constexpr HMIComponent VALUE03_ICON = {"home.value03_icon", 44}; ///< Value 3 icon (3 chars max) +constexpr HMIComponent VALUE04 = {"home.value04", 22}; ///< Value 4 text (30 chars max) +constexpr HMIComponent VALUE04_ICON = {"home.value04_icon", 23}; ///< Value 4 icon (3 chars max) + +// Chip components (top row) +constexpr HMIComponent CHIP_RELAY1 = {"home.chip_relay1", 11}; ///< Relay 1 chip (3 chars max) +constexpr HMIComponent CHIP_RELAY2 = {"home.chip_relay2", 12}; ///< Relay 2 chip (3 chars max) +constexpr HMIComponent CHIP_CLIMATE = {"home.chip_climate", 13}; ///< Climate chip (3 chars max) +constexpr HMIComponent CHIP01 = {"home.chip01", 14}; ///< Chip 1 (3 chars max) +constexpr HMIComponent CHIP02 = {"home.chip02", 15}; ///< Chip 2 (3 chars max) +constexpr HMIComponent CHIP03 = {"home.chip03", 16}; ///< Chip 3 (3 chars max) +constexpr HMIComponent CHIP04 = {"home.chip04", 17}; ///< Chip 4 (3 chars max) +constexpr HMIComponent CHIP05 = {"home.chip05", 18}; ///< Chip 5 (3 chars max) +constexpr HMIComponent CHIP06 = {"home.chip06", 19}; ///< Chip 6 (3 chars max) +constexpr HMIComponent CHIP07 = {"home.chip07", 20}; ///< Chip 7 (3 chars max) + +// Button components +constexpr HMIComponent BUTTON01 = {"home.button01", 35}; ///< Button 1 (3 chars max) +constexpr HMIComponent BUTTON02 = {"home.button02", 36}; ///< Button 2 (3 chars max) +constexpr HMIComponent BUTTON03 = {"home.button03", 37}; ///< Button 3 (3 chars max) +constexpr HMIComponent BUTTON04 = {"home.button04", 40}; ///< Button 4 (3 chars max) +constexpr HMIComponent BUTTON05 = {"home.button05", 41}; ///< Button 5 (3 chars max) +constexpr HMIComponent BUTTON06 = {"home.button06", 42}; ///< Button 6 (3 chars max) +constexpr HMIComponent BUTTON07 = {"home.button07", 31}; ///< Button 7 (3 chars max) + +// Bottom navigation buttons +constexpr HMIComponent BT_NOTIFIC = {"home.bt_notific", 28}; ///< Notifications button (3 chars max) +constexpr HMIComponent BT_QRCODE = {"home.bt_qrcode", 29}; ///< QR code button (3 chars max) +constexpr HMIComponent BT_ENTITIES = {"home.bt_entities", 30}; ///< Entities button (3 chars max) +constexpr HMIComponent BT_UTILITIES = {"home.bt_utilities", 43}; ///< Utilities button (3 chars max) + +// Footer text components +constexpr HMIComponent LEFT_BT_TEXT = {"home.left_bt_text", 9}; ///< Left button text (20 chars max) +constexpr HMIComponent RIGHT_BT_TEXT = {"home.right_bt_text", 10}; ///< Right button text (20 chars max) + +// System indicators +constexpr HMIComponent WIFI_ICON = {"home.wifi_icon", 26}; ///< WiFi status icon (5 chars max) +constexpr HMIComponent BT_ICON = {"home.bt_icon", 46}; ///< Bluetooth/system icon (5 chars max) + +// Variables (for reference - these are not visual components) +constexpr HMIComponent VAR_LASTCLICK = {"home.lastclick", 33}; ///< Last click variable +constexpr HMIComponent VAR_CLICK_COMP = {"home.click_comp", 38}; ///< Clicked component variable + +// Touch components +constexpr HMIComponent SWIPE = {"home.swipe", 1}; ///< Swipe gesture handler + +// Timers (for reference) +constexpr HMIComponent TIMER_SWIPESTORE = {"home.swipestore", 2}; ///< Swipe store timer +constexpr HMIComponent TIMER_SETTINGS = {"home.settings_timer", 34}; ///< Settings timer +constexpr HMIComponent TIMER_CLICK = {"home.click_timer", 39}; ///< Click timer +constexpr HMIComponent TIMER_SHORT_CLICK = {"home.short_click_tm", 47}; ///< Short click timer + +} // namespace home +} // namespace hmi } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_media_player.cpp b/components/nspanel_easy/page_media_player.cpp index d9151a40..dc2af3e9 100644 --- a/components/nspanel_easy/page_media_player.cpp +++ b/components/nspanel_easy/page_media_player.cpp @@ -7,9 +7,9 @@ namespace esphome { namespace nspanel_easy { - uint8_t last_volume_level = 0; - uint32_t last_media_duration = 0; - uint32_t last_media_position = 0; +uint8_t last_volume_level = 0; +uint32_t last_media_duration = 0; +uint32_t last_media_position = 0; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_media_player.h b/components/nspanel_easy/page_media_player.h index 67651625..5a22b31a 100644 --- a/components/nspanel_easy/page_media_player.h +++ b/components/nspanel_easy/page_media_player.h @@ -9,9 +9,9 @@ namespace esphome { namespace nspanel_easy { - extern uint8_t last_volume_level; // Last volume level from Home Assistant - extern uint32_t last_media_duration; // Last duration from Home Assistant - extern uint32_t last_media_position; // Last position from Home Assistant +extern uint8_t last_volume_level; // Last volume level from Home Assistant +extern uint32_t last_media_duration; // Last duration from Home Assistant +extern uint32_t last_media_position; // Last position from Home Assistant } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_notification.cpp b/components/nspanel_easy/page_notification.cpp index 66b85056..aec099a0 100644 --- a/components/nspanel_easy/page_notification.cpp +++ b/components/nspanel_easy/page_notification.cpp @@ -7,10 +7,10 @@ namespace esphome { namespace nspanel_easy { - std::string notification_label; - std::string notification_text; - uint16_t notification_icon_color_normal = 52857; - uint16_t notification_icon_color_unread = 63488; +std::string notification_label; +std::string notification_text; +uint16_t notification_icon_color_normal = 52857; +uint16_t notification_icon_color_unread = 63488; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_notification.h b/components/nspanel_easy/page_notification.h index 00fc3ede..16fb4195 100644 --- a/components/nspanel_easy/page_notification.h +++ b/components/nspanel_easy/page_notification.h @@ -10,10 +10,10 @@ namespace esphome { namespace nspanel_easy { - extern std::string notification_label; - extern std::string notification_text; - extern uint16_t notification_icon_color_normal; - extern uint16_t notification_icon_color_unread; +extern std::string notification_label; +extern std::string notification_text; +extern uint16_t notification_icon_color_normal; +extern uint16_t notification_icon_color_unread; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_qrcode.h b/components/nspanel_easy/page_qrcode.h index 56cd2df4..9ac471aa 100644 --- a/components/nspanel_easy/page_qrcode.h +++ b/components/nspanel_easy/page_qrcode.h @@ -17,42 +17,37 @@ namespace esphome { namespace nspanel_easy { - namespace hmi { - namespace qrcode { - - /** - * @namespace qrcode - * @brief Components for the QR Code page. - * - * Component ID mapping for the QR Code page - * Based on the Nextion HMI design file. - * Note: All components are local scope, so names don't include page prefix. - */ - - // Page definition - constexpr HMIComponent PAGE = {"qrcode", 17}; ///< QR Code page (index 17 in page_names array) - - // Display components - constexpr HMIComponent QRCODE_LABEL = {"qrcode_label", 1}; ///< QR code label text (100 chars max) - - // Button components - constexpr HMIComponent BUTTON_BACK = {"button_back", 2}; ///< Back button (3 chars max) - returns to home - - // The QRcode box - constexpr HMIComponent QR = {"qr", 3}; ///< The QRcode itself - - // All visual components for iteration - constexpr HMIComponent ALL[] = { - PAGE, - QRCODE_LABEL, - BUTTON_BACK, - QR - }; - - constexpr size_t COMPONENT_COUNT = sizeof(ALL) / sizeof(ALL[0]); - - } // namespace qrcode - } // namespace hmi +namespace hmi { +namespace qrcode { + +/** + * @namespace qrcode + * @brief Components for the QR Code page. + * + * Component ID mapping for the QR Code page + * Based on the Nextion HMI design file. + * Note: All components are local scope, so names don't include page prefix. + */ + +// Page definition +constexpr HMIComponent PAGE = {"qrcode", 17}; ///< QR Code page (index 17 in page_names array) + +// Display components +constexpr HMIComponent QRCODE_LABEL = {"qrcode_label", 1}; ///< QR code label text (100 chars max) + +// Button components +constexpr HMIComponent BUTTON_BACK = {"button_back", 2}; ///< Back button (3 chars max) - returns to home + +// The QRcode box +constexpr HMIComponent QR = {"qr", 3}; ///< The QRcode itself + +// All visual components for iteration +constexpr HMIComponent ALL[] = {PAGE, QRCODE_LABEL, BUTTON_BACK, QR}; + +constexpr size_t COMPONENT_COUNT = sizeof(ALL) / sizeof(ALL[0]); + +} // namespace qrcode +} // namespace hmi } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_utilities.cpp b/components/nspanel_easy/page_utilities.cpp index dc82c43c..1dbd6c96 100644 --- a/components/nspanel_easy/page_utilities.cpp +++ b/components/nspanel_easy/page_utilities.cpp @@ -4,7 +4,7 @@ #include "icons.h" #include "page_utilities.h" -#include // For malloc/free +#include // For malloc/free #ifdef USE_ESP_IDF #include "esp_heap_caps.h" #elif defined(USE_ARDUINO) @@ -14,49 +14,43 @@ namespace esphome { namespace nspanel_easy { - bool page_utilities_enabled = false; - uint16_t page_utilities_icon_color = Colors::GRAY_LIGHT; +bool page_utilities_enabled = false; +uint16_t page_utilities_icon_color = Colors::GRAY_LIGHT; - static constexpr size_t UTILITIES_GROUPS_COUNT = 8; +static constexpr size_t UTILITIES_GROUPS_COUNT = 8; - std::vector> UtilitiesGroups; - - void resetUtilitiesGroups() { - static constexpr UtilitiesGroupValues INITIAL_UTILITIES_GROUPS[8] = { - {"grid", "", "", 0}, - {"group01", "", "", 0}, - {"group02", "", "", 0}, - {"group03", "", "", 0}, - {"group04", "", "", 0}, - {"group05", "", "", 0}, - {"group06", "", "", 0}, - {"home", "", "", 0} - }; - - UtilitiesGroups.assign(INITIAL_UTILITIES_GROUPS, INITIAL_UTILITIES_GROUPS + 8); - } +std::vector> UtilitiesGroups; + +void resetUtilitiesGroups() { + static constexpr UtilitiesGroupValues INITIAL_UTILITIES_GROUPS[8] = { + {"grid", "", "", 0}, {"group01", "", "", 0}, {"group02", "", "", 0}, {"group03", "", "", 0}, + {"group04", "", "", 0}, {"group05", "", "", 0}, {"group06", "", "", 0}, {"home", "", "", 0}}; - uint8_t findUtilitiesGroupIndex(const char* group_id) { - if (UtilitiesGroups.empty()) return UINT8_MAX; - - int low = 0; - int high = UtilitiesGroups.size() - 1; + UtilitiesGroups.assign(INITIAL_UTILITIES_GROUPS, INITIAL_UTILITIES_GROUPS + 8); +} - while (low <= high) { - int mid = low + (high - low) / 2; - int cmp = std::strcmp(UtilitiesGroups[mid].group_id, group_id); +uint8_t findUtilitiesGroupIndex(const char *group_id) { + if (UtilitiesGroups.empty()) + return UINT8_MAX; - if (cmp < 0) { - low = mid + 1; - } else if (cmp > 0) { - high = mid - 1; - } else { - return static_cast(mid); // Found - } - } + int low = 0; + int high = UtilitiesGroups.size() - 1; - return UINT8_MAX; // Not found + while (low <= high) { + int mid = low + (high - low) / 2; + int cmp = std::strcmp(UtilitiesGroups[mid].group_id, group_id); + + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return static_cast(mid); // Found } + } + + return UINT8_MAX; // Not found +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/page_utilities.h b/components/nspanel_easy/page_utilities.h index ed96745e..a72a998c 100644 --- a/components/nspanel_easy/page_utilities.h +++ b/components/nspanel_easy/page_utilities.h @@ -6,7 +6,7 @@ #include // For std::min #include -#include // For std::strcpy +#include // For std::strcpy #include #include #include "esphome/core/defines.h" @@ -25,119 +25,118 @@ namespace esphome { namespace nspanel_easy { - extern bool page_utilities_enabled; - extern uint16_t page_utilities_icon_color; - - struct UtilitiesGroupValues { - char group_id[8]; // 7 characters + null terminator - char value1[11]; // 10 characters + null terminator - char value2[11]; // 10 characters + null terminator - int8_t direction; - }; - - extern std::vector> UtilitiesGroups; - - void resetUtilitiesGroups(); - uint8_t findUtilitiesGroupIndex(const char* group_id); - - /** - * Copies the contents of a std::string to a fixed-size char array, ensuring - * null termination. The destination array size is automatically deduced. - * Designed for fixed-size char arrays only. - * - * @param dest A reference to the destination char array. - * @param src The source std::string to copy. - */ - template - void copyStringToCharArray(char (&dest)[N], const std::string& src) { - // Ensure we do not exceed the buffer size, leaving space for the null terminator - size_t length = std::min(src.size(), N - 1); - - // Copy the string data into the destination buffer - std::strncpy(dest, src.c_str(), length); - - // Explicitly null-terminate the destination buffer - dest[length] = '\0'; - } - - namespace hmi { - namespace utilities { - - /** - * @namespace utilities - * @brief Components for the Utilities dashboard page. - * - * Component ID mapping for the Utilities page (index 27 in page_names array) - * Based on the typical Nextion HMI design patterns. - */ - - // Page definition - constexpr HMIComponent PAGE = {"utilities", 27}; ///< Utilities page (index 27 in page_names array) - - // Title components (global scope - accessible from other pages) - constexpr HMIComponent TITLE = {"utilities.title", 1}; ///< Page title display - constexpr HMIComponent TITLE_ICON = {"utilities.title_icon", 2}; ///< Title icon - - // Grid group components - constexpr HMIComponent GRID_LABEL = {"utilities.grid_label", 10}; ///< Grid area label - constexpr HMIComponent GRID_ICON = {"utilities.grid_icon", 11}; ///< Grid area icon - constexpr HMIComponent GRID_VALUE1 = {"utilities.grid_value1", 12}; ///< Grid value 1 - constexpr HMIComponent GRID_VALUE2 = {"utilities.grid_value2", 13}; ///< Grid value 2 - - // Home group components - constexpr HMIComponent HOME_LABEL = {"utilities.home_label", 20}; ///< Home area label - constexpr HMIComponent HOME_ICON = {"utilities.home_icon", 21}; ///< Home area icon - constexpr HMIComponent HOME_VALUE1 = {"utilities.home_value1", 22}; ///< Home value 1 - constexpr HMIComponent HOME_VALUE2 = {"utilities.home_value2", 23}; ///< Home value 2 - - // Group 1 components - constexpr HMIComponent GROUP01_LABEL = {"utilities.group01_label", 30}; ///< Group 1 label - constexpr HMIComponent GROUP01_ICON = {"utilities.group01_icon", 31}; ///< Group 1 icon - constexpr HMIComponent GROUP01_VALUE1 = {"utilities.group01_value1", 32}; ///< Group 1 value 1 - constexpr HMIComponent GROUP01_VALUE2 = {"utilities.group01_value2", 33}; ///< Group 1 value 2 - constexpr HMIComponent GROUP01_LINE = {"utilities.group01_line", 34}; ///< Group 1 flow line - - // Group 2 components - constexpr HMIComponent GROUP02_LABEL = {"utilities.group02_label", 40}; ///< Group 2 label - constexpr HMIComponent GROUP02_ICON = {"utilities.group02_icon", 41}; ///< Group 2 icon - constexpr HMIComponent GROUP02_VALUE1 = {"utilities.group02_value1", 42}; ///< Group 2 value 1 - constexpr HMIComponent GROUP02_VALUE2 = {"utilities.group02_value2", 43}; ///< Group 2 value 2 - constexpr HMIComponent GROUP02_LINE = {"utilities.group02_line", 44}; ///< Group 2 flow line - - // Group 3 components - constexpr HMIComponent GROUP03_LABEL = {"utilities.group03_label", 50}; ///< Group 3 label - constexpr HMIComponent GROUP03_ICON = {"utilities.group03_icon", 51}; ///< Group 3 icon - constexpr HMIComponent GROUP03_VALUE1 = {"utilities.group03_value1", 52}; ///< Group 3 value 1 - constexpr HMIComponent GROUP03_VALUE2 = {"utilities.group03_value2", 53}; ///< Group 3 value 2 - constexpr HMIComponent GROUP03_LINE = {"utilities.group03_line", 54}; ///< Group 3 flow line - - // Group 4 components - constexpr HMIComponent GROUP04_LABEL = {"utilities.group04_label", 60}; ///< Group 4 label - constexpr HMIComponent GROUP04_ICON = {"utilities.group04_icon", 61}; ///< Group 4 icon - constexpr HMIComponent GROUP04_VALUE1 = {"utilities.group04_value1", 62}; ///< Group 4 value 1 - constexpr HMIComponent GROUP04_VALUE2 = {"utilities.group04_value2", 63}; ///< Group 4 value 2 - constexpr HMIComponent GROUP04_LINE = {"utilities.group04_line", 64}; ///< Group 4 flow line - - // Group 5 components - constexpr HMIComponent GROUP05_LABEL = {"utilities.group05_label", 70}; ///< Group 5 label - constexpr HMIComponent GROUP05_ICON = {"utilities.group05_icon", 71}; ///< Group 5 icon - constexpr HMIComponent GROUP05_VALUE1 = {"utilities.group05_value1", 72}; ///< Group 5 value 1 - constexpr HMIComponent GROUP05_VALUE2 = {"utilities.group05_value2", 73}; ///< Group 5 value 2 - constexpr HMIComponent GROUP05_LINE = {"utilities.group05_line", 74}; ///< Group 5 flow line - - // Group 6 components - constexpr HMIComponent GROUP06_LABEL = {"utilities.group06_label", 80}; ///< Group 6 label - constexpr HMIComponent GROUP06_ICON = {"utilities.group06_icon", 81}; ///< Group 6 icon - constexpr HMIComponent GROUP06_VALUE1 = {"utilities.group06_value1", 82}; ///< Group 6 value 1 - constexpr HMIComponent GROUP06_VALUE2 = {"utilities.group06_value2", 83}; ///< Group 6 value 2 - constexpr HMIComponent GROUP06_LINE = {"utilities.group06_line", 84}; ///< Group 6 flow line - - // Navigation button - constexpr HMIComponent BUTTON_BACK = {"button_back", 90}; ///< Back button (local scope) - - } // namespace utilities - } // namespace hmi +extern bool page_utilities_enabled; +extern uint16_t page_utilities_icon_color; + +struct UtilitiesGroupValues { + char group_id[8]; // 7 characters + null terminator + char value1[11]; // 10 characters + null terminator + char value2[11]; // 10 characters + null terminator + int8_t direction; +}; + +extern std::vector> UtilitiesGroups; + +void resetUtilitiesGroups(); +uint8_t findUtilitiesGroupIndex(const char *group_id); + +/** + * Copies the contents of a std::string to a fixed-size char array, ensuring + * null termination. The destination array size is automatically deduced. + * Designed for fixed-size char arrays only. + * + * @param dest A reference to the destination char array. + * @param src The source std::string to copy. + */ +template void copyStringToCharArray(char (&dest)[N], const std::string &src) { + // Ensure we do not exceed the buffer size, leaving space for the null terminator + size_t length = std::min(src.size(), N - 1); + + // Copy the string data into the destination buffer + std::strncpy(dest, src.c_str(), length); + + // Explicitly null-terminate the destination buffer + dest[length] = '\0'; +} + +namespace hmi { +namespace utilities { + +/** + * @namespace utilities + * @brief Components for the Utilities dashboard page. + * + * Component ID mapping for the Utilities page (index 27 in page_names array) + * Based on the typical Nextion HMI design patterns. + */ + +// Page definition +constexpr HMIComponent PAGE = {"utilities", 27}; ///< Utilities page (index 27 in page_names array) + +// Title components (global scope - accessible from other pages) +constexpr HMIComponent TITLE = {"utilities.title", 1}; ///< Page title display +constexpr HMIComponent TITLE_ICON = {"utilities.title_icon", 2}; ///< Title icon + +// Grid group components +constexpr HMIComponent GRID_LABEL = {"utilities.grid_label", 10}; ///< Grid area label +constexpr HMIComponent GRID_ICON = {"utilities.grid_icon", 11}; ///< Grid area icon +constexpr HMIComponent GRID_VALUE1 = {"utilities.grid_value1", 12}; ///< Grid value 1 +constexpr HMIComponent GRID_VALUE2 = {"utilities.grid_value2", 13}; ///< Grid value 2 + +// Home group components +constexpr HMIComponent HOME_LABEL = {"utilities.home_label", 20}; ///< Home area label +constexpr HMIComponent HOME_ICON = {"utilities.home_icon", 21}; ///< Home area icon +constexpr HMIComponent HOME_VALUE1 = {"utilities.home_value1", 22}; ///< Home value 1 +constexpr HMIComponent HOME_VALUE2 = {"utilities.home_value2", 23}; ///< Home value 2 + +// Group 1 components +constexpr HMIComponent GROUP01_LABEL = {"utilities.group01_label", 30}; ///< Group 1 label +constexpr HMIComponent GROUP01_ICON = {"utilities.group01_icon", 31}; ///< Group 1 icon +constexpr HMIComponent GROUP01_VALUE1 = {"utilities.group01_value1", 32}; ///< Group 1 value 1 +constexpr HMIComponent GROUP01_VALUE2 = {"utilities.group01_value2", 33}; ///< Group 1 value 2 +constexpr HMIComponent GROUP01_LINE = {"utilities.group01_line", 34}; ///< Group 1 flow line + +// Group 2 components +constexpr HMIComponent GROUP02_LABEL = {"utilities.group02_label", 40}; ///< Group 2 label +constexpr HMIComponent GROUP02_ICON = {"utilities.group02_icon", 41}; ///< Group 2 icon +constexpr HMIComponent GROUP02_VALUE1 = {"utilities.group02_value1", 42}; ///< Group 2 value 1 +constexpr HMIComponent GROUP02_VALUE2 = {"utilities.group02_value2", 43}; ///< Group 2 value 2 +constexpr HMIComponent GROUP02_LINE = {"utilities.group02_line", 44}; ///< Group 2 flow line + +// Group 3 components +constexpr HMIComponent GROUP03_LABEL = {"utilities.group03_label", 50}; ///< Group 3 label +constexpr HMIComponent GROUP03_ICON = {"utilities.group03_icon", 51}; ///< Group 3 icon +constexpr HMIComponent GROUP03_VALUE1 = {"utilities.group03_value1", 52}; ///< Group 3 value 1 +constexpr HMIComponent GROUP03_VALUE2 = {"utilities.group03_value2", 53}; ///< Group 3 value 2 +constexpr HMIComponent GROUP03_LINE = {"utilities.group03_line", 54}; ///< Group 3 flow line + +// Group 4 components +constexpr HMIComponent GROUP04_LABEL = {"utilities.group04_label", 60}; ///< Group 4 label +constexpr HMIComponent GROUP04_ICON = {"utilities.group04_icon", 61}; ///< Group 4 icon +constexpr HMIComponent GROUP04_VALUE1 = {"utilities.group04_value1", 62}; ///< Group 4 value 1 +constexpr HMIComponent GROUP04_VALUE2 = {"utilities.group04_value2", 63}; ///< Group 4 value 2 +constexpr HMIComponent GROUP04_LINE = {"utilities.group04_line", 64}; ///< Group 4 flow line + +// Group 5 components +constexpr HMIComponent GROUP05_LABEL = {"utilities.group05_label", 70}; ///< Group 5 label +constexpr HMIComponent GROUP05_ICON = {"utilities.group05_icon", 71}; ///< Group 5 icon +constexpr HMIComponent GROUP05_VALUE1 = {"utilities.group05_value1", 72}; ///< Group 5 value 1 +constexpr HMIComponent GROUP05_VALUE2 = {"utilities.group05_value2", 73}; ///< Group 5 value 2 +constexpr HMIComponent GROUP05_LINE = {"utilities.group05_line", 74}; ///< Group 5 flow line + +// Group 6 components +constexpr HMIComponent GROUP06_LABEL = {"utilities.group06_label", 80}; ///< Group 6 label +constexpr HMIComponent GROUP06_ICON = {"utilities.group06_icon", 81}; ///< Group 6 icon +constexpr HMIComponent GROUP06_VALUE1 = {"utilities.group06_value1", 82}; ///< Group 6 value 1 +constexpr HMIComponent GROUP06_VALUE2 = {"utilities.group06_value2", 83}; ///< Group 6 value 2 +constexpr HMIComponent GROUP06_LINE = {"utilities.group06_line", 84}; ///< Group 6 flow line + +// Navigation button +constexpr HMIComponent BUTTON_BACK = {"button_back", 90}; ///< Back button (local scope) + +} // namespace utilities +} // namespace hmi } // namespace nspanel_easy } // namespace esphome -#endif // NSPANEL_EASY_PAGE_UTILITIES +#endif // NSPANEL_EASY_PAGE_UTILITIES diff --git a/components/nspanel_easy/pages.cpp b/components/nspanel_easy/pages.cpp index ef74c101..d0a7bac1 100644 --- a/components/nspanel_easy/pages.cpp +++ b/components/nspanel_easy/pages.cpp @@ -6,9 +6,9 @@ namespace esphome { namespace nspanel_easy { - uint8_t current_page_id = 0; - uint8_t last_page_id = UINT8_MAX; - uint8_t wakeup_page_id = 1; +uint8_t current_page_id = 0; +uint8_t last_page_id = UINT8_MAX; +uint8_t wakeup_page_id = 1; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/pages.h b/components/nspanel_easy/pages.h index 268fff44..5a500297 100644 --- a/components/nspanel_easy/pages.h +++ b/components/nspanel_easy/pages.h @@ -10,84 +10,59 @@ #include "esphome/core/string_ref.h" // For StringRef /** -* @file pages.h -* Defines constants and functions related to page names for the NSPanel HA Blueprint project. -*/ + * @file pages.h + * Defines constants and functions related to page names for the NSPanel HA Blueprint project. + */ namespace esphome { namespace nspanel_easy { - // Constants - /** - * A compile-time constant array containing the names of pages. - * These names correspond to various pages of the Nextion TFT file in use, - * such as settings, home, weather information, and more. - */ - constexpr const char* const page_names[] = { - "boot", - "home", - "weather01", - "weather02", - "weather03", - "weather04", - "weather05", - "climate", - "settings", - "screensaver", - "light", - "cover", - "buttonpage01", - "buttonpage02", - "buttonpage03", - "buttonpage04", - "notification", - "qrcode", - "entitypage01", - "entitypage02", - "entitypage03", - "entitypage04", - "fan", - "alarm", - "keyb_num", - "media_player", - "confirm", - "utilities", - "home_smpl", - "debug", - "water_heater" - }; +// Constants +/** + * A compile-time constant array containing the names of pages. + * These names correspond to various pages of the Nextion TFT file in use, + * such as settings, home, weather information, and more. + */ +constexpr const char *const page_names[] = { + "boot", "home", "weather01", "weather02", "weather03", "weather04", "weather05", + "climate", "settings", "screensaver", "light", "cover", "buttonpage01", "buttonpage02", + "buttonpage03", "buttonpage04", "notification", "qrcode", "entitypage01", "entitypage02", "entitypage03", + "entitypage04", "fan", "alarm", "keyb_num", "media_player", "confirm", "utilities", + "home_smpl", "debug", "water_heater"}; - constexpr size_t PAGE_COUNT = sizeof(page_names) / sizeof(page_names[0]); - static_assert(PAGE_COUNT <= UINT8_MAX, "PAGE_COUNT exceeds uint8_t range"); +constexpr size_t PAGE_COUNT = sizeof(page_names) / sizeof(page_names[0]); +static_assert(PAGE_COUNT <= UINT8_MAX, "PAGE_COUNT exceeds uint8_t range"); - // Global system flags - initialized to 0 (all flags false) - extern uint8_t current_page_id; - extern uint8_t last_page_id; - extern uint8_t wakeup_page_id; +// Global system flags - initialized to 0 (all flags false) +extern uint8_t current_page_id; +extern uint8_t last_page_id; +extern uint8_t wakeup_page_id; - /** - * Retrieves the index of a given page name within the page_names array. - * - * @param page_name The name of the page to find. - * @return The index of the page_name in the page_names array. If the page_name - * is not found, returns UINT8_MAX as an indicator that no matching page was found. - */ - inline uint8_t get_page_id(const char* page_name) { - if (page_name == nullptr || *page_name == '\0') return UINT8_MAX; - for (uint8_t i = 0; i < PAGE_COUNT; ++i) { - if (strcmp(page_names[i], page_name) == 0) - return i; - } - return UINT8_MAX; - } - inline uint8_t get_page_id(const esphome::StringRef& page_name) { - if (page_name.empty()) return UINT8_MAX; - for (uint8_t i = 0; i < PAGE_COUNT; ++i) { - if (page_name == page_names[i]) - return i; - } - return UINT8_MAX; - } +/** + * Retrieves the index of a given page name within the page_names array. + * + * @param page_name The name of the page to find. + * @return The index of the page_name in the page_names array. If the page_name + * is not found, returns UINT8_MAX as an indicator that no matching page was found. + */ +inline uint8_t get_page_id(const char *page_name) { + if (page_name == nullptr || *page_name == '\0') + return UINT8_MAX; + for (uint8_t i = 0; i < PAGE_COUNT; ++i) { + if (strcmp(page_names[i], page_name) == 0) + return i; + } + return UINT8_MAX; +} +inline uint8_t get_page_id(const esphome::StringRef &page_name) { + if (page_name.empty()) + return UINT8_MAX; + for (uint8_t i = 0; i < PAGE_COUNT; ++i) { + if (page_name == page_names[i]) + return i; + } + return UINT8_MAX; +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/text.cpp b/components/nspanel_easy/text.cpp index 7fe3834d..0ab7f98b 100644 --- a/components/nspanel_easy/text.cpp +++ b/components/nspanel_easy/text.cpp @@ -9,174 +9,181 @@ namespace esphome { namespace nspanel_easy { - std::string adjustDecimalSeparator(const std::string& input, char decimalSeparator) { - if (decimalSeparator == '.') { - return input; - } - - // Find the end of the numeric part - size_t numericEnd = 0; - for (; numericEnd < input.size(); ++numericEnd) { - const char c = input[numericEnd]; - if (!((c >= '0' && c <= '9') || c == '.' || c == '-' || c == ',')) { - break; - } - } - - // If no numeric part found, return original - if (numericEnd == 0) { - return input; - } - - std::string numericPart = input.substr(0, numericEnd); - - // Validate that numericPart is actually a valid number - char* end; - strtod(numericPart.c_str(), &end); // Result unused, only checking validity - - if (end != numericPart.c_str() && *end == '\0') { - // Find and replace decimal point - size_t decimalPointPos = numericPart.find('.'); - if (decimalPointPos != std::string::npos) { - numericPart[decimalPointPos] = decimalSeparator; - } - - // Append suffix if any - if (numericEnd < input.size()) { - numericPart += input.substr(numericEnd); - } - return numericPart; - } - - return input; +std::string adjustDecimalSeparator(const std::string &input, char decimalSeparator) { + if (decimalSeparator == '.') { + return input; + } + + // Find the end of the numeric part + size_t numericEnd = 0; + for (; numericEnd < input.size(); ++numericEnd) { + const char c = input[numericEnd]; + if (!((c >= '0' && c <= '9') || c == '.' || c == '-' || c == ',')) { + break; } + } - std::string wrapText(const std::string& text_to_display, - uint8_t line_length_limit, - uint8_t bytes_per_char) { - // Safety check for overly long text - if (text_to_display.length() > 1000) { - return "ERROR: Text too long"; - } - // Invalid parameters check - if (line_length_limit == 0 || bytes_per_char == 0) { - return "ERROR: Invalid line length"; - } - // Early exit for already formatted text - if (text_to_display.find("\\r") != std::string::npos) { - return text_to_display; - } - - const uint16_t max_line_length = line_length_limit * bytes_per_char; - const uint16_t text_len = static_cast(text_to_display.length()); - - // If text is short enough, no wrapping needed - if (text_len <= max_line_length) { - return text_to_display; - } - - // Pre-allocate string to avoid reallocations - optimize for flash memory - std::string wrapped_text; - wrapped_text.reserve(text_len + 20); // Reserve space for line breaks - - uint16_t start = 0; - - while (start < text_len) { - // Skip leading spaces - while (start < text_len && text_to_display[start] == ' ') { - ++start; - } - - if (start >= text_len) { - break; // End of text reached - } // if - - // Find end position - uint16_t end = start + max_line_length; - if (end >= text_len) { - end = text_len; - } else { - // Find word boundary by looking backwards for space - uint16_t word_end = end; - while (word_end > start && text_to_display[word_end] != ' ') { - --word_end; - } - - // If we found a space within reasonable distance, use it - if (word_end > start) { - end = word_end; - } - // Otherwise force break at max_line_length (handles long words) - } // else - - // Add text segment - wrapped_text.append(text_to_display, start, end - start); - - // Add line break if not at end of text - if (end < text_len) { - wrapped_text += "\\r"; - - // Skip spaces at the break point to avoid leading spaces on next line - while (end < text_len && text_to_display[end] == ' ') { - ++end; - } - } // if - - start = end; - } // while - - return wrapped_text; + // If no numeric part found, return original + if (numericEnd == 0) { + return input; + } + + std::string numericPart = input.substr(0, numericEnd); + + // Validate that numericPart is actually a valid number + char *end; + strtod(numericPart.c_str(), &end); // Result unused, only checking validity + + if (end != numericPart.c_str() && *end == '\0') { + // Find and replace decimal point + size_t decimalPointPos = numericPart.find('.'); + if (decimalPointPos != std::string::npos) { + numericPart[decimalPointPos] = decimalSeparator; } - bool isStringInList(const std::string& strToSearch, std::initializer_list list) { - return std::any_of(list.begin(), list.end(), - [&strToSearch](const std::string& str) { return strToSearch == str; }); + // Append suffix if any + if (numericEnd < input.size()) { + numericPart += input.substr(numericEnd); + } + return numericPart; + } + + return input; +} + +std::string wrapText(const std::string &text_to_display, uint8_t line_length_limit, uint8_t bytes_per_char) { + // Safety check for overly long text + if (text_to_display.length() > 1000) { + return "ERROR: Text too long"; + } + // Invalid parameters check + if (line_length_limit == 0 || bytes_per_char == 0) { + return "ERROR: Invalid line length"; + } + // Early exit for already formatted text + if (text_to_display.find("\\r") != std::string::npos) { + return text_to_display; + } + + const uint16_t max_line_length = line_length_limit * bytes_per_char; + const uint16_t text_len = static_cast(text_to_display.length()); + + // If text is short enough, no wrapping needed + if (text_len <= max_line_length) { + return text_to_display; + } + + // Pre-allocate string to avoid reallocations - optimize for flash memory + std::string wrapped_text; + wrapped_text.reserve(text_len + 20); // Reserve space for line breaks + + uint16_t start = 0; + + while (start < text_len) { + // Skip leading spaces + while (start < text_len && text_to_display[start] == ' ') { + ++start; } - uint32_t decode_nextion_icon_utf8(const char* bytes, size_t length) { - // Nextion icons are encoded as 1-3 byte UTF-8 sequences only - if (!bytes || length == 0 || length > 3) { - return 0; // Invalid: null, empty, or too long for Nextion icon - } - - unsigned char byte = static_cast(bytes[0]); - uint32_t code_point = 0; - - // 1-byte sequence (0x00-0x7F) - if ((byte & 0x80) == 0x00) { - code_point = byte; - } - // 2-byte sequence (0xC0-0xDF) - else if ((byte & 0xE0) == 0xC0) { - if (length < 2 || (bytes[1] & 0xC0) != 0x80) { - return 0; // Invalid: insufficient length or bad continuation byte - } - code_point = ((byte & 0x1F) << 6) | (bytes[1] & 0x3F); - // Reject overlong encodings - if (code_point < 0x80) { - return 0; - } - } - // 3-byte sequence (0xE0-0xEF) - else if ((byte & 0xF0) == 0xE0) { - if (length < 3 || (bytes[1] & 0xC0) != 0x80 || (bytes[2] & 0xC0) != 0x80) { - return 0; // Invalid: insufficient length or bad continuation bytes - } - code_point = ((byte & 0x0F) << 12) | - ((bytes[1] & 0x3F) << 6) | - (bytes[2] & 0x3F); - // Reject overlong encodings and surrogate code points - if (code_point < 0x800 || (code_point >= 0xD800 && code_point <= 0xDFFF)) { - return 0; - } - } - // Invalid: 4-byte sequences or invalid leading byte - else { - return 0; - } - - return code_point; + if (start >= text_len) { + break; // End of text reached + } // if + + // Find end position + uint16_t end = start + max_line_length; + if (end >= text_len) { + end = text_len; + } else { + // Find word boundary by looking backwards for space + uint16_t word_end = end; + while (word_end > start && text_to_display[word_end] != ' ') { + --word_end; + } + + // If we found a space within reasonable distance, use it + if (word_end > start) { + end = word_end; + } + // Otherwise force break at max_line_length (handles long words) + } // else + + // Add text segment + wrapped_text.append(text_to_display, start, end - start); + + // Add line break if not at end of text + if (end < text_len) { + wrapped_text += "\\r"; + + // Skip spaces at the break point to avoid leading spaces on next line + while (end < text_len && text_to_display[end] == ' ') { + ++end; + } + } // if + + start = end; + } // while + + return wrapped_text; +} + +bool isStringInList(const std::string &strToSearch, std::initializer_list list) { + return std::any_of(list.begin(), list.end(), [&strToSearch](const std::string &str) { return strToSearch == str; }); +} + +uint32_t decode_nextion_icon_utf8(const char *bytes, size_t length) { + // Nextion icons are encoded as 1-3 byte UTF-8 sequences only + if (!bytes || length == 0 || length > 3) { + return 0; // Invalid: null, empty, or too long for Nextion icon + } + + unsigned char byte = static_cast(bytes[0]); + uint32_t code_point = 0; + + // 1-byte sequence (0x00-0x7F) + if ((byte & 0x80) == 0x00) { + code_point = byte; + } + // 2-byte sequence (0xC0-0xDF) + else if ((byte & 0xE0) == 0xC0) { + if (length < 2 || (bytes[1] & 0xC0) != 0x80) { + return 0; // Invalid: insufficient length or bad continuation byte + } + code_point = ((byte & 0x1F) << 6) | (bytes[1] & 0x3F); + // Reject overlong encodings + if (code_point < 0x80) { + return 0; + } + } + // 3-byte sequence (0xE0-0xEF) + else if ((byte & 0xF0) == 0xE0) { + if (length < 3 || (bytes[1] & 0xC0) != 0x80 || (bytes[2] & 0xC0) != 0x80) { + return 0; // Invalid: insufficient length or bad continuation bytes + } + code_point = ((byte & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) | (bytes[2] & 0x3F); + // Reject overlong encodings and surrogate code points + if (code_point < 0x800 || (code_point >= 0xD800 && code_point <= 0xDFFF)) { + return 0; } + } + // Invalid: 4-byte sequences or invalid leading byte + else { + return 0; + } + + return code_point; +} + +void replace_all(std::string &str, const char *token, const char *value) { + const size_t token_len = strlen(token); + if (token_len == 0) + return; // Nothing to replace + const size_t value_len = strlen(value); + size_t pos = 0; + while ((pos = str.find(token, pos)) != std::string::npos) { + str.replace(pos, token_len, value); + pos += value_len; // Advance past the replacement to avoid infinite loop + } +} } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/text.h b/components/nspanel_easy/text.h index 03871807..5111aa4a 100644 --- a/components/nspanel_easy/text.h +++ b/components/nspanel_easy/text.h @@ -9,80 +9,92 @@ namespace esphome { namespace nspanel_easy { - /** - * Adjusts the decimal separator in a numeric string to the specified character. - * Only the first occurrence is replaced if it's a valid number followed by text. - * Returns the original string if it doesn't start with a valid number. - * - * @param input The string containing a numeric value followed by text. - * @param decimalSeparator The character to use as the decimal separator. - * @return A string with the adjusted decimal separator if valid; otherwise, the original string. - */ - std::string adjustDecimalSeparator(const std::string& input, char decimalSeparator); +/** + * Adjusts the decimal separator in a numeric string to the specified character. + * Only the first occurrence is replaced if it's a valid number followed by text. + * Returns the original string if it doesn't start with a valid number. + * + * @param input The string containing a numeric value followed by text. + * @param decimalSeparator The character to use as the decimal separator. + * @return A string with the adjusted decimal separator if valid; otherwise, the original string. + */ +std::string adjustDecimalSeparator(const std::string &input, char decimalSeparator); - /** - * Wraps text to fit within specified line length limits for Nextion displays. - * Uses word boundaries when possible and adds "\\r" line breaks for Nextion compatibility. - * - * Performance optimized for ESP32 with minimal memory allocations and flash usage. - * Supports both Arduino and ESP-IDF frameworks. - * - * @param text_to_display The input text to be wrapped. If already contains "\\r", returns as-is. - * @param line_length_limit Maximum number of characters per line (will be multiplied by bytes_per_char). - * @param bytes_per_char Multiplier for character width calculation (typically from mui_bytes_per_char). - * @return Wrapped text with "\\r" line breaks as std::string. - * - * @note Uses "\\r" as line break separator for Nextion display compatibility. - * @note Skips leading/trailing spaces and handles word boundaries intelligently. - * @note Returns original text if already formatted or if no wrapping is needed. - * @note Thread-safe: No shared state between calls. - */ - std::string wrapText(const std::string& text_to_display, - uint8_t line_length_limit, - uint8_t bytes_per_char); +/** + * Wraps text to fit within specified line length limits for Nextion displays. + * Uses word boundaries when possible and adds "\\r" line breaks for Nextion compatibility. + * + * Performance optimized for ESP32 with minimal memory allocations and flash usage. + * Supports both Arduino and ESP-IDF frameworks. + * + * @param text_to_display The input text to be wrapped. If already contains "\\r", returns as-is. + * @param line_length_limit Maximum number of characters per line (will be multiplied by bytes_per_char). + * @param bytes_per_char Multiplier for character width calculation (typically from mui_bytes_per_char). + * @return Wrapped text with "\\r" line breaks as std::string. + * + * @note Uses "\\r" as line break separator for Nextion display compatibility. + * @note Skips leading/trailing spaces and handles word boundaries intelligently. + * @note Returns original text if already formatted or if no wrapping is needed. + * @note Thread-safe: No shared state between calls. + */ +std::string wrapText(const std::string &text_to_display, uint8_t line_length_limit, uint8_t bytes_per_char); - /** - * Checks if a given string is present within a list of strings. - * - * @param strToSearch The string to search for within the list. - * @param list An initializer list of strings to search within. - * @return `true` if the target string is found within the list, `false` otherwise. - */ - bool isStringInList(const std::string& strToSearch, std::initializer_list list); +/** + * Checks if a given string is present within a list of strings. + * + * @param strToSearch The string to search for within the list. + * @param list An initializer list of strings to search within. + * @return `true` if the target string is found within the list, `false` otherwise. + */ +bool isStringInList(const std::string &strToSearch, std::initializer_list list); - uint32_t decode_nextion_icon_utf8(const char* bytes, size_t length); +uint32_t decode_nextion_icon_utf8(const char *bytes, size_t length); - /** - * @brief Compile-time string comparison for C++17/20. - * - * Compares two null-terminated C-strings at compile time. - * This function is constexpr-compatible and can be used in - * compile-time contexts where std::strcmp is not available - * (std::strcmp becomes constexpr only in C++23). - * - * @param a First null-terminated string to compare. - * @param b Second null-terminated string to compare. - * @return true if strings are identical, false otherwise. - * - * @note Both pointers must be valid and point to null-terminated strings. - * @note Comparison stops at the first difference or null terminator. - * - * Example usage: - * @code - * constexpr bool is_home = strings_equal(page_names[1], "home"); - * static_assert(strings_equal("test", "test")); // Compile-time check - * @endcode - */ - constexpr bool strings_equal(const char* a, const char* b) { - // Compare characters while both strings have non-null characters - while (*a && *b) { - if (*a != *b) return false; // Different characters found - ++a; - ++b; - } - // Strings are equal only if both reached null terminator - return *a == *b; - } +/** + * @brief Compile-time string comparison for C++17/20. + * + * Compares two null-terminated C-strings at compile time. + * This function is constexpr-compatible and can be used in + * compile-time contexts where std::strcmp is not available + * (std::strcmp becomes constexpr only in C++23). + * + * @param a First null-terminated string to compare. + * @param b Second null-terminated string to compare. + * @return true if strings are identical, false otherwise. + * + * @note Both pointers must be valid and point to null-terminated strings. + * @note Comparison stops at the first difference or null terminator. + * + * Example usage: + * @code + * constexpr bool is_home = strings_equal(page_names[1], "home"); + * static_assert(strings_equal("test", "test")); // Compile-time check + * @endcode + */ +constexpr bool strings_equal(const char *a, const char *b) { + // Compare characters while both strings have non-null characters + while (*a && *b) { + if (*a != *b) + return false; // Different characters found + ++a; + ++b; + } + // Strings are equal only if both reached null terminator + return *a == *b; +} + +/** + * @brief Replaces all occurrences of a token in a string with a given value. + * + * Iterates through the string replacing every occurrence of `token` with + * `value`. Safe for cases where `value` contains `token` as a substring, + * since the search position advances past each replacement. + * + * @param str The string to modify in place. + * @param token The substring to search for. + * @param value The replacement string. + */ +void replace_all(std::string &str, const char *token, const char *value); } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/versioning.cpp b/components/nspanel_easy/versioning.cpp index ac004b43..88b07088 100644 --- a/components/nspanel_easy/versioning.cpp +++ b/components/nspanel_easy/versioning.cpp @@ -7,8 +7,8 @@ namespace esphome { namespace nspanel_easy { - uint8_t version_blueprint = 0; - uint8_t version_tft = 0; +uint8_t version_blueprint = 0; +uint8_t version_tft = 0; } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/versioning.h b/components/nspanel_easy/versioning.h index 810e9458..22d90cfa 100644 --- a/components/nspanel_easy/versioning.h +++ b/components/nspanel_easy/versioning.h @@ -9,8 +9,8 @@ namespace esphome { namespace nspanel_easy { - extern uint8_t version_blueprint; // Blueprint version/revision - extern uint8_t version_tft; // TFT version/revision +extern uint8_t version_blueprint; // Blueprint version/revision +extern uint8_t version_tft; // TFT version/revision } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/weather.cpp b/components/nspanel_easy/weather.cpp index 4c5a4982..f10f64c2 100644 --- a/components/nspanel_easy/weather.cpp +++ b/components/nspanel_easy/weather.cpp @@ -7,12 +7,12 @@ namespace esphome { namespace nspanel_easy { - SunInfo sun_info = { - .is_up = true, // Safe daytime default before first blueprint update or SNTP sync +SunInfo sun_info = { + .is_up = true, // Safe daytime default before first blueprint update or SNTP sync .coord_received = false, // Coordinates not yet received - time proxy active - }; +}; - uint8_t weather_condition_index = 0; // Defaults to fallback until first blueprint update +uint8_t weather_condition_index = 0; // Defaults to fallback until first blueprint update } // namespace nspanel_easy } // namespace esphome diff --git a/components/nspanel_easy/weather.h b/components/nspanel_easy/weather.h index ac54aa19..bdb9ba50 100644 --- a/components/nspanel_easy/weather.h +++ b/components/nspanel_easy/weather.h @@ -30,204 +30,203 @@ namespace esphome { namespace nspanel_easy { - // ============================================================================= - // Sun elevation state - // ============================================================================= - - /** - * @brief Tracks sun elevation state and coordinate availability. - * - * When @p coord_received is @c false, @p is_up is derived from the local - * time as a rough proxy (06:00-18:00 = above horizon) until the blueprint - * sends valid coordinates and the ESPHome @c sun component takes over via - * its @c on_sunrise and @c on_sunset triggers. - * - * Both fields are intentionally kept together so callers can check - * @p coord_received and @p is_up in a single struct access. - */ - struct SunInfo { - bool is_up; ///< true when the sun is above the horizon - bool coord_received; ///< true once valid coordinates have been received from the blueprint - }; - - extern SunInfo sun_info; ///< Global sun elevation state for the weather engine - - // ============================================================================= - // Data structures - // ============================================================================= - - /** - * @brief Picture IDs for one condition in one theme variant. - * - * Sun-unaware conditions set @p sun_down equal to @p sun_up. - * The caller selects between them using @ref SunInfo::is_up or - * @c sun_component->is_above_horizon(). - */ - struct WeatherPicVariant { - uint16_t sun_up; ///< Picture ID when the sun is above the horizon - uint16_t sun_down; ///< Picture ID when the sun is below the horizon - }; - - /** - * @brief All picture variants for a single weather condition. - * - * Two device generations x two themes, each with a sun-up / sun-down split. - * A picture ID of 0 indicates the slot is not yet assigned. - */ - struct WeatherPics { - WeatherPicVariant legacy_light; ///< Legacy model, light theme - WeatherPicVariant legacy_dark; ///< Legacy model, dark theme - WeatherPicVariant new_light; ///< New model, light theme - WeatherPicVariant new_dark; ///< New model, dark theme - }; - - /** - * @brief Associates a normalised condition string with its picture set. - * - * Index 0 is always the sentinel / fallback entry (null key). - * Named entries occupy indices 1 and above. - */ - struct WeatherConditionEntry { - const char* key; ///< Condition name (underscore-separated, lower-case), or null for the fallback entry - WeatherPics pics; ///< Picture IDs for this condition, or fallback IDs for the sentinel - }; - - // ============================================================================= - // Lookup table - // ============================================================================= - - /// @brief Sun-unaware variant: same picture regardless of sun elevation. -#define WPV(id) { (id), (id) } - /// @brief Sun-aware variant: distinct pictures for day and night. -#define WPV2(up, down) { (up), (down) } - - /** - * @brief Lookup table mapping weather condition strings to picture IDs. - * - * Index 0 is the fallback entry (null key), used for unknown, unavailable, - * and any unrecognised condition strings Home Assistant may send. - * Named entries are sorted alphabetically from index 1 for readability. - * - * The linear search in @ref get_weather_index is O(n) over at most ~15 - * named entries - performance is not a concern at this scale. - * - * @note Picture IDs of 0 indicate unassigned slots - the display will - * not update for those combinations until IDs are filled in. - */ - constexpr WeatherConditionEntry WEATHER_CONDITIONS[] = { +// ============================================================================= +// Sun elevation state +// ============================================================================= + +/** + * @brief Tracks sun elevation state and coordinate availability. + * + * When @p coord_received is @c false, @p is_up is derived from the local + * time as a rough proxy (06:00-18:00 = above horizon) until the blueprint + * sends valid coordinates and the ESPHome @c sun component takes over via + * its @c on_sunrise and @c on_sunset triggers. + * + * Both fields are intentionally kept together so callers can check + * @p coord_received and @p is_up in a single struct access. + */ +struct SunInfo { + bool is_up; ///< true when the sun is above the horizon + bool coord_received; ///< true once valid coordinates have been received from the blueprint +}; + +extern SunInfo sun_info; ///< Global sun elevation state for the weather engine + +// ============================================================================= +// Data structures +// ============================================================================= + +/** + * @brief Picture IDs for one condition in one theme variant. + * + * Sun-unaware conditions set @p sun_down equal to @p sun_up. + * The caller selects between them using @ref SunInfo::is_up or + * @c sun_component->is_above_horizon(). + */ +struct WeatherPicVariant { + uint16_t sun_up; ///< Picture ID when the sun is above the horizon + uint16_t sun_down; ///< Picture ID when the sun is below the horizon +}; + +/** + * @brief All picture variants for a single weather condition. + * + * Two device generations x two themes, each with a sun-up / sun-down split. + * A picture ID of 0 indicates the slot is not yet assigned. + */ +struct WeatherPics { + WeatherPicVariant legacy_light; ///< Legacy model, light theme + WeatherPicVariant legacy_dark; ///< Legacy model, dark theme + WeatherPicVariant new_light; ///< New model, light theme + WeatherPicVariant new_dark; ///< New model, dark theme +}; + +/** + * @brief Associates a normalised condition string with its picture set. + * + * Index 0 is always the sentinel / fallback entry (null key). + * Named entries occupy indices 1 and above. + */ +struct WeatherConditionEntry { + const char *key; ///< Condition name (underscore-separated, lower-case), or null for the fallback entry + WeatherPics pics; ///< Picture IDs for this condition, or fallback IDs for the sentinel +}; + +// ============================================================================= +// Lookup table +// ============================================================================= + +/// @brief Sun-unaware variant: same picture regardless of sun elevation. +#define WPV(id) \ + { (id), (id) } +/// @brief Sun-aware variant: distinct pictures for day and night. +#define WPV2(up, down) \ + { (up), (down) } + +/** + * @brief Lookup table mapping weather condition strings to picture IDs. + * + * Index 0 is the fallback entry (null key), used for unknown, unavailable, + * and any unrecognised condition strings Home Assistant may send. + * Named entries are sorted alphabetically from index 1 for readability. + * + * The linear search in @ref get_weather_index is O(n) over at most ~15 + * named entries - performance is not a concern at this scale. + * + * @note Picture IDs of 0 indicate unassigned slots - the display will + * not update for those combinations until IDs are filled in. + */ +constexpr WeatherConditionEntry WEATHER_CONDITIONS[] = { // legacy_light legacy_dark new_light new_dark - { nullptr, { WPV(49), WPV(1), WPV(0), WPV(0) } }, ///< Index 0 - fallback - { "clear_night", { WPV2(50, 63), WPV2(2, 15), WPV2(14, 1), WPV2(27, 15) } }, ///< Index 1 - { "cloudy", { WPV(51), WPV(3), WPV(2), WPV(16) } }, ///< Index 2 - { "exceptional", { WPV2(61, 62), WPV2(13, 14), WPV(0), WPV(0) } }, ///< Index 3 - { "fog", { WPV(56), WPV(8), WPV(3), WPV(17) } }, ///< Index 4 - { "hail", { WPV(55), WPV(7), WPV(4), WPV(18) } }, ///< Index 5 - { "lightning", { WPV(58), WPV(10), WPV(5), WPV(19) } }, ///< Index 6 - { "lightning_rainy", { WPV2(61, 62), WPV2(13, 14), WPV(6), WPV(20) } }, ///< Index 7 - { "partlycloudy", { WPV2(59, 60), WPV2(11, 12), WPV2(7, 8), WPV2(21, 22) } }, ///< Index 8 - { "pouring", { WPV(53), WPV(5), WPV(9), WPV(23) } }, ///< Index 9 - { "rainy", { WPV(52), WPV(4), WPV(10), WPV(24) } }, ///< Index 10 - { "snowy", { WPV(54), WPV(6), WPV(11), WPV(25) } }, ///< Index 11 - { "snowy_rainy", { WPV(55), WPV(7), WPV(12), WPV(26) } }, ///< Index 12 - { "sunny", { WPV2(50, 63), WPV2(2, 15), WPV2(13, 1), WPV2(27, 15) } }, ///< Index 13 - { "windy", { WPV(57), WPV(9), WPV(14), WPV(28) } }, ///< Index 14 - { "windy_variant", { WPV(57), WPV(9), WPV(14), WPV(28) } }, ///< Index 15 - }; + {nullptr, {WPV(49), WPV(1), WPV(0), WPV(0)}}, ///< Index 0 - fallback + {"clear_night", {WPV2(50, 63), WPV2(2, 15), WPV2(14, 1), WPV2(27, 15)}}, ///< Index 1 + {"cloudy", {WPV(51), WPV(3), WPV(2), WPV(16)}}, ///< Index 2 + {"exceptional", {WPV2(61, 62), WPV2(13, 14), WPV(0), WPV(0)}}, ///< Index 3 + {"fog", {WPV(56), WPV(8), WPV(3), WPV(17)}}, ///< Index 4 + {"hail", {WPV(55), WPV(7), WPV(4), WPV(18)}}, ///< Index 5 + {"lightning", {WPV(58), WPV(10), WPV(5), WPV(19)}}, ///< Index 6 + {"lightning_rainy", {WPV2(61, 62), WPV2(13, 14), WPV(6), WPV(20)}}, ///< Index 7 + {"partlycloudy", {WPV2(59, 60), WPV2(11, 12), WPV2(7, 8), WPV2(21, 22)}}, ///< Index 8 + {"pouring", {WPV(53), WPV(5), WPV(9), WPV(23)}}, ///< Index 9 + {"rainy", {WPV(52), WPV(4), WPV(10), WPV(24)}}, ///< Index 10 + {"snowy", {WPV(54), WPV(6), WPV(11), WPV(25)}}, ///< Index 11 + {"snowy_rainy", {WPV(55), WPV(7), WPV(12), WPV(26)}}, ///< Index 12 + {"sunny", {WPV2(50, 63), WPV2(2, 15), WPV2(13, 1), WPV2(27, 15)}}, ///< Index 13 + {"windy", {WPV(57), WPV(9), WPV(14), WPV(28)}}, ///< Index 14 + {"windy_variant", {WPV(57), WPV(9), WPV(14), WPV(28)}}, ///< Index 15 +}; #undef WPV #undef WPV2 - // ============================================================================= - // Current condition state - // ============================================================================= - - /** - * @brief Index into @ref WEATHER_CONDITIONS for the current weather condition. - * - * Set by @ref get_weather_index when a new condition string is received from - * the blueprint. Defaults to 0 (fallback) until the first update arrives. - */ - extern uint8_t weather_condition_index; - - // ============================================================================= - // Helper functions - // ============================================================================= - - /** - * @brief Normalises a weather condition string for table lookup. - * - * Replaces hyphens with underscores in-place so that "clear-night" and - * "clear_night" both resolve to the same key. - * - * @param[in,out] buf Null-terminated string to normalise (modified in place). - * @param[in] size Size of the buffer including the null terminator. - */ - inline void normalise_weather_condition(char* buf, size_t size) { - for (size_t i = 0; i < size && buf[i] != '\0'; ++i) { - if (buf[i] == '-') - buf[i] = '_'; - } - } +// ============================================================================= +// Current condition state +// ============================================================================= - /** - * @brief Looks up the index of a weather condition string in @ref WEATHER_CONDITIONS. - * - * Normalises the input (hyphens to underscores) once before searching the - * table. The search starts at index 1, skipping the fallback entry at index 0. - * Returns 0 for any null, empty, or unrecognised condition string. - * - * @param condition Null-terminated condition string (hyphens or underscores). - * @return Index into @ref WEATHER_CONDITIONS, or 0 if not found. - */ - inline uint8_t get_weather_index(const char* condition) { - if (condition == nullptr || *condition == '\0') - return 0; - - char buf[32] = {}; - strncpy(buf, condition, sizeof(buf) - 1); - buf[sizeof(buf) - 1] = '\0'; // Ensure null-termination if input fills the buffer - normalise_weather_condition(buf, sizeof(buf)); - - constexpr uint8_t count = - sizeof(WEATHER_CONDITIONS) / sizeof(WEATHER_CONDITIONS[0]); - for (uint8_t i = 1; i < count; ++i) { - if (strcmp(WEATHER_CONDITIONS[i].key, buf) == 0) - return i; - } - return 0; // Fallback - } +/** + * @brief Index into @ref WEATHER_CONDITIONS for the current weather condition. + * + * Set by @ref get_weather_index when a new condition string is received from + * the blueprint. Defaults to 0 (fallback) until the first update arrives. + */ +extern uint8_t weather_condition_index; - /** - * @brief Returns the picture set for a given @ref WEATHER_CONDITIONS index. - * - * Clamps out-of-range indices to 0 (fallback) to guard against stale state. - * - * @param index Index into @ref WEATHER_CONDITIONS. - * @return Reference to the matching @ref WeatherPics. - */ - inline const WeatherPics& get_weather_pics(uint8_t index) { - constexpr uint8_t count = - sizeof(WEATHER_CONDITIONS) / sizeof(WEATHER_CONDITIONS[0]); - if (index >= count) - index = 0; // Clamp to fallback - return WEATHER_CONDITIONS[index].pics; +// ============================================================================= +// Helper functions +// ============================================================================= + +/** + * @brief Normalises a weather condition string for table lookup. + * + * Replaces hyphens with underscores in-place so that "clear-night" and + * "clear_night" both resolve to the same key. + * + * @param[in,out] buf Null-terminated string to normalise (modified in place). + * @param[in] size Size of the buffer including the null terminator. + */ +inline void normalise_weather_condition(char *buf, size_t size) { + for (size_t i = 0; i < size && buf[i] != '\0'; ++i) { + if (buf[i] == '-') + buf[i] = '_'; } +} - /** - * @brief Selects the correct picture variant based on device and theme. - * - * @param pics Picture set returned by @ref get_weather_pics. - * @param is_new_device @c true for the new device generation, @c false for legacy. - * @param is_dark_theme @c true when the dark theme is active. - * @return Reference to the matching @ref WeatherPicVariant. - */ - inline const WeatherPicVariant& select_weather_variant(const WeatherPics& pics, - bool is_new_device, - bool is_dark_theme) { - if (is_new_device) - return is_dark_theme ? pics.new_dark : pics.new_light; - return is_dark_theme ? pics.legacy_dark : pics.legacy_light; +/** + * @brief Looks up the index of a weather condition string in @ref WEATHER_CONDITIONS. + * + * Normalises the input (hyphens to underscores) once before searching the + * table. The search starts at index 1, skipping the fallback entry at index 0. + * Returns 0 for any null, empty, or unrecognised condition string. + * + * @param condition Null-terminated condition string (hyphens or underscores). + * @return Index into @ref WEATHER_CONDITIONS, or 0 if not found. + */ +inline uint8_t get_weather_index(const char *condition) { + if (condition == nullptr || *condition == '\0') + return 0; + + char buf[32] = {}; + strncpy(buf, condition, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; // Ensure null-termination if input fills the buffer + normalise_weather_condition(buf, sizeof(buf)); + + constexpr uint8_t count = sizeof(WEATHER_CONDITIONS) / sizeof(WEATHER_CONDITIONS[0]); + for (uint8_t i = 1; i < count; ++i) { + if (strcmp(WEATHER_CONDITIONS[i].key, buf) == 0) + return i; } + return 0; // Fallback +} + +/** + * @brief Returns the picture set for a given @ref WEATHER_CONDITIONS index. + * + * Clamps out-of-range indices to 0 (fallback) to guard against stale state. + * + * @param index Index into @ref WEATHER_CONDITIONS. + * @return Reference to the matching @ref WeatherPics. + */ +inline const WeatherPics &get_weather_pics(uint8_t index) { + constexpr uint8_t count = sizeof(WEATHER_CONDITIONS) / sizeof(WEATHER_CONDITIONS[0]); + if (index >= count) + index = 0; // Clamp to fallback + return WEATHER_CONDITIONS[index].pics; +} + +/** + * @brief Selects the correct picture variant based on device and theme. + * + * @param pics Picture set returned by @ref get_weather_pics. + * @param is_new_device @c true for the new device generation, @c false for legacy. + * @param is_dark_theme @c true when the dark theme is active. + * @return Reference to the matching @ref WeatherPicVariant. + */ +inline const WeatherPicVariant &select_weather_variant(const WeatherPics &pics, bool is_new_device, + bool is_dark_theme) { + if (is_new_device) + return is_dark_theme ? pics.new_dark : pics.new_light; + return is_dark_theme ? pics.legacy_dark : pics.legacy_light; +} } // namespace nspanel_easy } // namespace esphome diff --git a/esphome/nspanel_esphome_datetime.yaml b/esphome/nspanel_esphome_datetime.yaml index 8a869049..bee04b0a 100644 --- a/esphome/nspanel_esphome_datetime.yaml +++ b/esphome/nspanel_esphome_datetime.yaml @@ -72,38 +72,27 @@ script: ESPTime now = id(time_provider).now(); std::string time_format_str = id(mui_time_format); - // Looped token replacer — handles repeated tokens in the same format string - auto replace_token = [](std::string& str, const char* token, const char* value) { - size_t pos = 0; - const size_t token_len = strlen(token); - const size_t value_len = strlen(value); - while ((pos = str.find(token, pos)) != std::string::npos) { - str.replace(pos, token_len, value); - pos += value_len; - } - }; - // Resolve %-H (no-padding 24h hour) - replace_token(time_format_str, "%-H", to_string(static_cast(now.hour)).c_str()); + nspanel_easy::replace_all(time_format_str, "%-H", to_string(static_cast(now.hour)).c_str()); // Resolve %-I (no-padding 12h hour) const int hour12 = (now.hour == 0) ? 12 : (now.hour > 12) ? (now.hour - 12) : now.hour; - replace_token(time_format_str, "%-I", to_string(hour12).c_str()); + nspanel_easy::replace_all(time_format_str, "%-I", to_string(hour12).c_str()); const char* const meridiem_text = (now.hour < 12) ? - "${LOCALIZATION[LANG].meridiem.am}" : - "${LOCALIZATION[LANG].meridiem.pm}"; + "${LOCALIZATION[LANG].meridiem.am}" : + "${LOCALIZATION[LANG].meridiem.pm}"; const bool has_meridiem = (time_format_str.find("%p") != std::string::npos); if (current_page->state == "screensaver" and id(screensaver_display_time)) { std::string time_format_str_sleep = time_format_str; - replace_token(time_format_str_sleep, "%p", meridiem_text); + nspanel_easy::replace_all(time_format_str_sleep, "%p", meridiem_text); disp1->set_component_text("text", now.strftime(time_format_str_sleep).c_str()); } // Resolve %p for home page — strip from time string, set meridiem component separately // Trim both ends after replacement to avoid leading/trailing whitespace - replace_token(time_format_str, "%p", ""); + nspanel_easy::replace_all(time_format_str, "%p", ""); while (!time_format_str.empty() && time_format_str.front() == ' ') time_format_str.erase(time_format_str.begin()); while (!time_format_str.empty() && time_format_str.back() == ' ') @@ -167,19 +156,10 @@ script: const int mon = dt.month - 1; std::string date_str = id(mui_date_format); - auto replace_token = [](std::string& str, const char* token, const char* value) { - size_t pos = 0; - const size_t token_len = strlen(token); - const size_t value_len = strlen(value); - while ((pos = str.find(token, pos)) != std::string::npos) { - str.replace(pos, token_len, value); - pos += value_len; - } - }; - replace_token(date_str, "%A", weekdays[dow]); - replace_token(date_str, "%a", weekdays_short[dow]); - replace_token(date_str, "%B", months[mon]); - replace_token(date_str, "%b", months_short[mon]); + nspanel_easy::replace_all(date_str, "%A", weekdays[dow]); + nspanel_easy::replace_all(date_str, "%a", weekdays_short[dow]); + nspanel_easy::replace_all(date_str, "%B", months[mon]); + nspanel_easy::replace_all(date_str, "%b", months_short[mon]); disp1->set_component_text(component.c_str(), dt.strftime(date_str).c_str());