diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a7c337f --- /dev/null +++ b/.clang-format @@ -0,0 +1,137 @@ +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: true +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/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..5e486e6 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,127 @@ +--- +Checks: >- + *, + -abseil-*, + -android-*, + -boost-*, + -bugprone-macro-parentheses, + -cert-dcl50-cpp, + -cert-err58-cpp, + -clang-analyzer-core.CallAndMessage, + -clang-analyzer-osx.*, + -clang-analyzer-security.*, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-c-copy-assignment-signature, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-const-cast, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-static-cast-downcast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + -fuchsia-*, + -fuchsia-default-arguments, + -fuchsia-multiple-inheritance, + -fuchsia-overloaded-operator, + -fuchsia-statically-constructed-objects, + -google-build-using-namespace, + -google-explicit-constructor, + -google-readability-braces-around-statements, + -google-readability-casting, + -google-readability-todo, + -google-runtime-int, + -google-runtime-references, + -hicpp-*, + -llvm-header-guard, + -llvm-include-order, + -misc-unconventional-assign-operator, + -misc-unused-parameters, + -modernize-deprecated-headers, + -modernize-pass-by-value, + -modernize-pass-by-value, + -modernize-return-braced-init-list, + -modernize-use-auto, + -modernize-use-default-member-init, + -modernize-use-equals-default, + -mpi-*, + -objc-*, + -performance-unnecessary-value-param, + -readability-braces-around-statements, + -readability-else-after-return, + -readability-implicit-bool-conversion, + -readability-named-parameter, + -readability-redundant-member-init, + -warnings-as-errors, + -zircon-* +WarningsAsErrors: '*' +HeaderFilterRegex: '^.*/src/esphome/.*' +AnalyzeTemporaryDtors: false +FormatStyle: google +CheckOptions: + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: readability-identifier-naming.LocalVariableCase + value: 'lower_case' + - key: readability-identifier-naming.ClassCase + value: 'CamelCase' + - key: readability-identifier-naming.StructCase + value: 'CamelCase' + - key: readability-identifier-naming.EnumCase + value: 'CamelCase' + - key: readability-identifier-naming.EnumConstantCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.StaticConstantCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.StaticVariableCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.GlobalConstantCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.ParameterCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMemberPrefix + value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED' + - key: readability-identifier-naming.PrivateMethodPrefix + value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED' + - key: readability-identifier-naming.ClassMemberCase + value: 'lower_case' + - key: readability-identifier-naming.ClassMemberCase + value: 'lower_case' + - key: readability-identifier-naming.ProtectedMemberCase + value: 'lower_case' + - key: readability-identifier-naming.ProtectedMemberSuffix + value: '_' + - key: readability-identifier-naming.FunctionCase + value: 'lower_case' + - key: readability-identifier-naming.ClassMethodCase + value: 'lower_case' + - key: readability-identifier-naming.ProtectedMethodCase + value: 'lower_case' + - key: readability-identifier-naming.ProtectedMethodSuffix + value: '_' + - key: readability-identifier-naming.VirtualMethodCase + value: 'lower_case' + - key: readability-identifier-naming.VirtualMethodSuffix + value: '' diff --git a/binary_sensor/touch_binary_sensor.h b/binary_sensor/touch_binary_sensor.h index 97f8144..93ea254 100644 --- a/binary_sensor/touch_binary_sensor.h +++ b/binary_sensor/touch_binary_sensor.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../common.h" #include "../front_panel_hal.h" @@ -13,40 +13,44 @@ namespace bslamp2 { * and touch slider on the front panel of the Xiaomi Mijia Bedside Lamp 2. */ class XiaomiBslamp2TouchBinarySensor : public binary_sensor::BinarySensor, public Component { -public: - void set_parent(FrontPanelHAL *front_panel) { - front_panel_ = front_panel; - } + public: + void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } - void set_for(int part) { - for_ = part; - } + void set_for(int part) { for_ = part; } - void setup() { - front_panel_->add_on_event_callback( - [this](EVENT ev) { - auto part_in_event = ev & FLAG_PART_MASK; - if (for_ == 0 || part_in_event == for_) { - auto new_state = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; - this->publish_state(new_state); - } - } - ); - } + void setup() { + front_panel_->add_on_event_callback([this](EVENT ev) { + auto part_in_event = ev & FLAG_PART_MASK; + if (for_ == 0 || part_in_event == for_) { + auto new_state = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; + this->publish_state(new_state); + } + }); + } - void dump_config() { - ESP_LOGCONFIG(TAG, "Front panel binary_sensor:"); - ESP_LOGCONFIG(TAG, " For: %s", - for_ & FLAG_PART_POWER ? "power button" : - for_ & FLAG_PART_COLOR ? "color button" : - for_ & FLAG_PART_SLIDER ? "slider" : "ERR"); - } + void dump_config() { + ESP_LOGCONFIG(TAG, "Front panel binary_sensor:"); + ESP_LOGCONFIG(TAG, " For: %s", format_part()); + } -protected: - FrontPanelHAL *front_panel_; - EVENT for_ = 0; + protected: + FrontPanelHAL *front_panel_; + EVENT for_ = 0; + + const char *format_part() { + switch (for_) { + case FLAG_PART_POWER: + return "power button"; + case FLAG_PART_COLOR: + return "color button"; + case FLAG_PART_SLIDER: + return "slider"; + default: + return "ERR"; + } + } }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/common.h b/common.h index 44ebf17..3952317 100644 --- a/common.h +++ b/common.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once namespace esphome { namespace xiaomi { @@ -7,6 +7,6 @@ namespace bslamp2 { // Used for logging purposes. static const char *TAG = "xiaomi_bslamp2"; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/front_panel_hal.h b/front_panel_hal.h index 2235298..b26e00c 100644 --- a/front_panel_hal.h +++ b/front_panel_hal.h @@ -1,10 +1,10 @@ -#pragma once +#pragma once -#include #include "common.h" +#include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" #include "esphome/core/esphal.h" -#include "esphome/components/i2c/i2c.h" +#include namespace esphome { namespace xiaomi { @@ -13,20 +13,22 @@ namespace bslamp2 { static const uint8_t MSG_LEN = 7; using MSG = uint8_t[7]; +// clang-format off + // The commands that are supported by the front panel component. -static const MSG READY_FOR_EV = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; -static const MSG TURN_ON = { 0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00 }; -static const MSG TURN_OFF = { 0x02, 0x03, 0x0C, 0x00, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_1 = { 0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_2 = { 0x02, 0x03, 0x5F, 0x00, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_3 = { 0x02, 0x03, 0x5F, 0x80, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_4 = { 0x02, 0x03, 0x5F, 0xC0, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_5 = { 0x02, 0x03, 0x5F, 0xE0, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_6 = { 0x02, 0x03, 0x5F, 0xF0, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_7 = { 0x02, 0x03, 0x5F, 0xF8, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_8 = { 0x02, 0x03, 0x5F, 0xFC, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_9 = { 0x02, 0x03, 0x5F, 0xFE, 0x64, 0x00, 0x00 }; -static const MSG SET_LEVEL_10 = { 0x02, 0x03, 0x5F, 0xFF, 0x64, 0x00, 0x00 }; +static const MSG READY_FOR_EV = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; +static const MSG TURN_ON = {0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00}; +static const MSG TURN_OFF = {0x02, 0x03, 0x0C, 0x00, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_1 = {0x02, 0x03, 0x5E, 0x00, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_2 = {0x02, 0x03, 0x5F, 0x00, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_3 = {0x02, 0x03, 0x5F, 0x80, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_4 = {0x02, 0x03, 0x5F, 0xC0, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_5 = {0x02, 0x03, 0x5F, 0xE0, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_6 = {0x02, 0x03, 0x5F, 0xF0, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_7 = {0x02, 0x03, 0x5F, 0xF8, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_8 = {0x02, 0x03, 0x5F, 0xFC, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_9 = {0x02, 0x03, 0x5F, 0xFE, 0x64, 0x00, 0x00}; +static const MSG SET_LEVEL_10 = {0x02, 0x03, 0x5F, 0xFF, 0x64, 0x00, 0x00}; using EVENT = uint16_t; @@ -40,7 +42,7 @@ using EVENT = uint16_t; // 2-4 part 000 part unknown // 001 power button // 010 color button -// 100 slider +// 100 slider // 5-6 type 00 type unknown // 01 touch // 10 release @@ -71,89 +73,100 @@ static const EVENT FLAG_LEVEL_SHIFT = 6; static const EVENT FLAG_LEVEL_MASK = 0b11111000000; static const EVENT FLAG_LEVEL_UNKNOWN = 0b00000000000; +// clang-format on + /** * This class implements a parser that translates event byte codes from the * Xiaomi Mijia Bedside Lamp 2 into usable events. */ class FrontPanelEventParser { -public: - /** - * Parse the provided event byte code (7 bytes long). - * Returns a unique integer event code that describes the parsed event. - */ - EVENT parse(uint8_t *m) { - EVENT ev = FLAG_INIT; - - // All events use the prefix [04:04:01:00]. - if (m[0] != 0x04 || m[1] != 0x04 || m[2] != 0x01 || m[3] != 0x00) { - return error_(ev, m, "prefix is not 04:04:01:00"); - } + public: + /** + * Parse the provided event byte code (7 bytes long). + * Returns a unique integer event code that describes the parsed event. + */ + EVENT parse(uint8_t *m) { + EVENT ev = FLAG_INIT; - // The next byte determines the part that is touched. - // All remaining bytes specify the event for that part. - switch (m[4]) { - case 0x01: // power button - case 0x02: // color button - ev |= (m[4] == 0x01 ? FLAG_PART_POWER : FLAG_PART_COLOR); - if (m[5] == 0x01 && m[6] == (0x02 + m[4])) { - ev |= FLAG_TYPE_TOUCH; - } else if (m[5] == 0x02 && m[6] == (0x03 + m[4])) { - ev |= FLAG_TYPE_RELEASE; - } else { - return error_(ev, m, "invalid event type for button"); - } - break; - case 0x03: // slider touch - case 0x04: // slider release - ev |= FLAG_PART_SLIDER; - ev |= (m[4] == 0x03 ? FLAG_TYPE_TOUCH : FLAG_TYPE_RELEASE); - if ((m[6] - m[5] - m[4] - 0x01) != 0) { - return error_(ev, m, "invalid slider level crc"); - } else if (m[5] > 0x16 || m[5] < 0x01) { - return error_(ev, m, "out of bounds slider value"); - } else { - auto level = 0x17 - m[5]; - ev |= (level << FLAG_LEVEL_SHIFT); - } - break; - default: - return error_(ev, m, "invalid part id"); - return ev; - } - - // All parsing rules passed. This event is valid. - ESP_LOGD(TAG, "Front panel I2C event parsed: code=%d", ev); - ev |= FLAG_OK; + // All events use the prefix [04:04:01:00]. + if (m[0] != 0x04 || m[1] != 0x04 || m[2] != 0x01 || m[3] != 0x00) { + return error_(ev, m, "prefix is not 04:04:01:00"); + } + // The next byte determines the part that is touched. + // All remaining bytes specify the event for that part. + switch (m[4]) { + case 0x01: // power button + case 0x02: // color button + ev |= (m[4] == 0x01 ? FLAG_PART_POWER : FLAG_PART_COLOR); + if (m[5] == 0x01 && m[6] == (0x02 + m[4])) + ev |= FLAG_TYPE_TOUCH; + else if (m[5] == 0x02 && m[6] == (0x03 + m[4])) + ev |= FLAG_TYPE_RELEASE; + else + return error_(ev, m, "invalid event type for button"); + break; + case 0x03: // slider touch + case 0x04: // slider release + ev |= FLAG_PART_SLIDER; + ev |= (m[4] == 0x03 ? FLAG_TYPE_TOUCH : FLAG_TYPE_RELEASE); + if ((m[6] - m[5] - m[4] - 0x01) != 0) + return error_(ev, m, "invalid slider level crc"); + else if (m[5] > 0x16 || m[5] < 0x01) + return error_(ev, m, "out of bounds slider value"); + else { + auto level = 0x17 - m[5]; + ev |= (level << FLAG_LEVEL_SHIFT); + } + break; + default: + return error_(ev, m, "invalid part id"); return ev; } -protected: - bool has_(EVENT ev, EVENT mask, EVENT flag) { - return (ev & mask) == flag; - } + // All parsing rules passed. This event is valid. + ESP_LOGD(TAG, "Front panel I2C event parsed: code=%d", ev); + ev |= FLAG_OK; - EVENT error_(EVENT ev, uint8_t *m, const char* msg) { - ESP_LOGE(TAG, "Front panel I2C event error:"); - ESP_LOGE(TAG, " Error: %s", msg); - ESP_LOGE(TAG, " Event: [%02x:%02x:%02x:%02x:%02x:%02x:%02x]", - m[0], m[1], m[2], m[3], m[4], m[5], m[6]); - ESP_LOGE(TAG, " Parsed part: %s", - (has_(ev, FLAG_PART_MASK, FLAG_PART_POWER) ? "power button" : - has_(ev, FLAG_PART_MASK, FLAG_PART_COLOR) ? "color button" : - has_(ev, FLAG_PART_MASK, FLAG_PART_SLIDER) ? "slider" : "n/a")); - ESP_LOGE(TAG, " Parsed event type: %s", - (has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_TOUCH) ? "touch" : - has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_RELEASE) ? "release" : "n/a")); - if (has_(ev, FLAG_PART_MASK, FLAG_PART_SLIDER)) { - auto level = (ev & FLAG_LEVEL_MASK) >> FLAG_LEVEL_SHIFT; - if (level > 0) { - ESP_LOGE(TAG, " Parsed slider level: %d", level); - } - } + return ev; + } - return ev; + protected: + bool has_(EVENT ev, EVENT mask, EVENT flag) { return (ev & mask) == flag; } + + EVENT error_(EVENT ev, uint8_t *m, const char *msg) { + ESP_LOGE(TAG, "Front panel I2C event error:"); + ESP_LOGE(TAG, " Error: %s", msg); + ESP_LOGE(TAG, " Event: [%02x:%02x:%02x:%02x:%02x:%02x:%02x]", m[0], m[1], m[2], m[3], m[4], m[5], m[6]); + ESP_LOGE(TAG, " Parsed part: %s", format_part(ev)); + ESP_LOGE(TAG, " Parsed event type: %s", format_event_type(ev)); + if (has_(ev, FLAG_PART_MASK, FLAG_PART_SLIDER)) { + auto level = (ev & FLAG_LEVEL_MASK) >> FLAG_LEVEL_SHIFT; + if (level > 0) { + ESP_LOGE(TAG, " Parsed slider level: %d", level); + } } + + return ev; + } + + const char *format_part(EVENT ev) { + if (has_(ev, FLAG_PART_MASK, FLAG_PART_POWER)) + return "power button"; + if (has_(ev, FLAG_PART_MASK, FLAG_PART_COLOR)) + return "color button"; + if (has_(ev, FLAG_PART_MASK, FLAG_PART_SLIDER)) + return "slider'; + return "n/a"; + } + + const char *format_event_type(EVENT ev) { + if (has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_TOUCH)) + return "touch"; + if (has_(ev, FLAG_TYPE_MASK, FLAG_TYPE_RELEASE)) + return "release"; + return "n/a"; + } }; /** @@ -164,86 +177,82 @@ protected: * the actual buttons and slider components. */ class FrontPanelHAL : public Component, public i2c::I2CDevice { -public: - FrontPanelEventParser event; + public: + FrontPanelEventParser event; - /** - * Set the GPIO pin that is used by the front panel to notify the ESP - * that a touch/release event can be read using I2C. - */ - void set_trigger_pin(GPIOPin *pin) { trigger_pin_ = pin; } + /** + * Set the GPIO pin that is used by the front panel to notify the ESP + * that a touch/release event can be read using I2C. + */ + void set_trigger_pin(GPIOPin *pin) { trigger_pin_ = pin; } - void add_on_event_callback(std::function &&callback) { - event_callback_.add(std::move(callback)); - } + void add_on_event_callback(std::function &&callback) { event_callback_.add(std::move(callback)); } - void setup() { - ESP_LOGCONFIG(TAG, "Setting up I2C trigger pin interrupt..."); - trigger_pin_->setup(); - trigger_pin_->attach_interrupt( - FrontPanelHAL::isr, this, FALLING); - } + void setup() { + ESP_LOGCONFIG(TAG, "Setting up I2C trigger pin interrupt..."); + trigger_pin_->setup(); + trigger_pin_->attach_interrupt(FrontPanelHAL::isr, this, FALLING); + } - void dump_config() { - ESP_LOGCONFIG(TAG, "FrontPanelHAL:"); - LOG_PIN(" I2C interrupt pin: ", trigger_pin_); - } + void dump_config() { + ESP_LOGCONFIG(TAG, "FrontPanelHAL:"); + LOG_PIN(" I2C interrupt pin: ", trigger_pin_); + } - void loop() { - // Read and publish front panel events. - auto current_event_id = event_id_; - if (current_event_id != last_event_id_) { - last_event_id_ = current_event_id; - MSG message; - if (write_bytes_raw(READY_FOR_EV, MSG_LEN) && - read_bytes_raw(message, MSG_LEN)) { - auto ev = event.parse(message); - if (ev & FLAG_OK) { - event_callback_.call(ev); - } - } + void loop() { + // Read and publish front panel events. + auto current_event_id = event_id_; + if (current_event_id != last_event_id_) { + last_event_id_ = current_event_id; + MSG message; + if (write_bytes_raw(READY_FOR_EV, MSG_LEN) && read_bytes_raw(message, MSG_LEN)) { + auto ev = event.parse(message); + if (ev & FLAG_OK) { + event_callback_.call(ev); } + } } + } - /** - * Sets the front panel illumination to the provided level (0.0 - 1.0). - * - * Level 0.0 means: turn off the front panel illumination. - * The other levels are translated to one of the available levels, - * represented by the level indicator (i.e. the illumination of the - * slider bar.) - */ - void set_light_level(float level) { - if (level == 0.0f) - write_bytes_raw(TURN_OFF, MSG_LEN); - else if (level < 0.15) - write_bytes_raw(SET_LEVEL_1, MSG_LEN); - else if (level < 0.25) - write_bytes_raw(SET_LEVEL_2, MSG_LEN); - else if (level < 0.35) - write_bytes_raw(SET_LEVEL_3, MSG_LEN); - else if (level < 0.45) - write_bytes_raw(SET_LEVEL_4, MSG_LEN); - else if (level < 0.55) - write_bytes_raw(SET_LEVEL_5, MSG_LEN); - else if (level < 0.65) - write_bytes_raw(SET_LEVEL_6, MSG_LEN); - else if (level < 0.75) - write_bytes_raw(SET_LEVEL_7, MSG_LEN); - else if (level < 0.85) - write_bytes_raw(SET_LEVEL_8, MSG_LEN); - else if (level < 0.95) - write_bytes_raw(SET_LEVEL_9, MSG_LEN); - else - write_bytes_raw(SET_LEVEL_10, MSG_LEN); - } + /** + * Sets the front panel illumination to the provided level (0.0 - 1.0). + * + * Level 0.0 means: turn off the front panel illumination. + * The other levels are translated to one of the available levels, + * represented by the level indicator (i.e. the illumination of the + * slider bar.) + */ + void set_light_level(float level) { + if (level == 0.0f) + write_bytes_raw(TURN_OFF, MSG_LEN); + else if (level < 0.15) + write_bytes_raw(SET_LEVEL_1, MSG_LEN); + else if (level < 0.25) + write_bytes_raw(SET_LEVEL_2, MSG_LEN); + else if (level < 0.35) + write_bytes_raw(SET_LEVEL_3, MSG_LEN); + else if (level < 0.45) + write_bytes_raw(SET_LEVEL_4, MSG_LEN); + else if (level < 0.55) + write_bytes_raw(SET_LEVEL_5, MSG_LEN); + else if (level < 0.65) + write_bytes_raw(SET_LEVEL_6, MSG_LEN); + else if (level < 0.75) + write_bytes_raw(SET_LEVEL_7, MSG_LEN); + else if (level < 0.85) + write_bytes_raw(SET_LEVEL_8, MSG_LEN); + else if (level < 0.95) + write_bytes_raw(SET_LEVEL_9, MSG_LEN); + else + write_bytes_raw(SET_LEVEL_10, MSG_LEN); + } -protected: - GPIOPin *trigger_pin_; - static void isr(FrontPanelHAL *store); - volatile int event_id_ = 0; - int last_event_id_ = 0; - CallbackManager event_callback_{}; + protected: + GPIOPin *trigger_pin_; + static void isr(FrontPanelHAL *store); + volatile int event_id_ = 0; + int last_event_id_ = 0; + CallbackManager event_callback_{}; }; /** @@ -254,10 +263,8 @@ protected: * is increment a simple event id counter. The main loop of the component * will take care of actually reading and processing the event. */ -void ICACHE_RAM_ATTR HOT FrontPanelHAL::isr(FrontPanelHAL *store) { - store->event_id_++; -} +void ICACHE_RAM_ATTR HOT FrontPanelHAL::isr(FrontPanelHAL *store) { store->event_id_++; } -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/automation.h b/light/automation.h index 2460798..1a2afdc 100644 --- a/light/automation.h +++ b/light/automation.h @@ -1,63 +1,63 @@ #pragma once -#include -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" #include "light_output.h" #include "presets.h" +#include namespace esphome { namespace xiaomi { namespace bslamp2 { - + class BrightnessTrigger : public Trigger { -public: - explicit BrightnessTrigger(XiaomiBslamp2LightOutput *parent) { - parent->add_on_state_callback([this](light::LightColorValues values) { - auto new_brightness = values.get_brightness(); - if (values.get_state() == 0) { - new_brightness = 0.0f; - } - new_brightness = roundf(new_brightness * 100.0f) / 100.0f; - if (last_brightness_ != new_brightness) { - trigger(new_brightness); - last_brightness_ = new_brightness; - } - }); - } -protected: - float last_brightness_ = -1.0f; + public: + explicit BrightnessTrigger(XiaomiBslamp2LightOutput *parent) { + parent->add_on_state_callback([this](light::LightColorValues values) { + auto new_brightness = values.get_brightness(); + if (values.get_state() == 0) + new_brightness = 0.0f; + new_brightness = roundf(new_brightness * 100.0f) / 100.0f; + if (last_brightness_ != new_brightness) { + trigger(new_brightness); + last_brightness_ = new_brightness; + } + }); + } + + protected: + float last_brightness_ = -1.0f; }; template class ActivatePresetAction : public Action { -public: - explicit ActivatePresetAction(PresetsContainer *presets) : presets_(presets) {} - - TEMPLATABLE_VALUE(std::string, operation); - TEMPLATABLE_VALUE(std::string, group); - TEMPLATABLE_VALUE(std::string, preset); - - void play(Ts... x) override { - auto operation = this->operation_.value(x...); - - if (operation == "next_group") { - presets_->activate_next_group(); - } else if (operation == "next_preset") { - presets_->activate_next_preset(); - } else if (operation == "activate_group") { - auto group = this->group_.value(x...); - presets_->activate_group(group); - } else if (operation == "activate_preset") { - auto group = this->group_.value(x...); - auto preset = this->preset_.value(x...); - presets_->activate_preset(group, preset); - } + public: + explicit ActivatePresetAction(PresetsContainer *presets) : presets_(presets) {} + + TEMPLATABLE_VALUE(std::string, operation); + TEMPLATABLE_VALUE(std::string, group); + TEMPLATABLE_VALUE(std::string, preset); + + void play(Ts... x) override { + auto operation = this->operation_.value(x...); + + if (operation == "next_group") { + presets_->activate_next_group(); + } else if (operation == "next_preset") { + presets_->activate_next_preset(); + } else if (operation == "activate_group") { + auto group = this->group_.value(x...); + presets_->activate_group(group); + } else if (operation == "activate_preset") { + auto group = this->group_.value(x...); + auto preset = this->preset_.value(x...); + presets_->activate_preset(group, preset); } + } -protected: - PresetsContainer *presets_; + protected: + PresetsContainer *presets_; }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_instant_handler.h b/light/color_instant_handler.h index 4bacb05..dc2eca9 100644 --- a/light/color_instant_handler.h +++ b/light/color_instant_handler.h @@ -4,17 +4,17 @@ #include #include "../common.h" -#include "gpio_outputs.h" -#include "color_off.h" #include "color_night_light.h" -#include "color_white_light.h" +#include "color_off.h" #include "color_rgb_light.h" +#include "color_white_light.h" +#include "gpio_outputs.h" namespace esphome { namespace xiaomi { namespace bslamp2 { -/** +/** * This class translates LightColorValues into GPIO duty cycles that * can be used for representing a requested light color on the * physical device. @@ -27,34 +27,33 @@ namespace bslamp2 { * - RGB light: based on RGB values + brightness */ class ColorInstantHandler : public GPIOOutputs { -public: - bool set_light_color_values(light::LightColorValues v) { - // The actual implementation of the various light modes is in - // separated targeted classes. These classes are called here - // in a chain of command-like pattern, to let the first one - // that can handle the light settings do the honours. - if (off_light_->set_light_color_values(v)) - off_light_->copy_to(this); - else if (night_light_->set_light_color_values(v)) - night_light_->copy_to(this); - else if (white_light_->set_light_color_values(v)) - white_light_->copy_to(this); - else if (rgb_light_->set_light_color_values(v)) - rgb_light_->copy_to(this); - else - throw std::logic_error( - "None of the GPIOOutputs classes handles the requested light state"); + public: + bool set_light_color_values(light::LightColorValues v) { + // The actual implementation of the various light modes is in + // separated targeted classes. These classes are called here + // in a chain of command-like pattern, to let the first one + // that can handle the light settings do the honours. + if (off_light_->set_light_color_values(v)) + off_light_->copy_to(this); + else if (night_light_->set_light_color_values(v)) + night_light_->copy_to(this); + else if (white_light_->set_light_color_values(v)) + white_light_->copy_to(this); + else if (rgb_light_->set_light_color_values(v)) + rgb_light_->copy_to(this); + else + throw std::logic_error("None of the GPIOOutputs classes handles the requested light state"); - return true; - } + return true; + } -protected: - GPIOOutputs *off_light_ = new ColorOff(); - GPIOOutputs *rgb_light_ = new ColorRGBLight(); - GPIOOutputs *white_light_ = new ColorWhiteLight(); - GPIOOutputs *night_light_ = new ColorNightLight(); + protected: + GPIOOutputs *off_light_ = new ColorOff(); + GPIOOutputs *rgb_light_ = new ColorRGBLight(); + GPIOOutputs *white_light_ = new ColorWhiteLight(); + GPIOOutputs *night_light_ = new ColorNightLight(); }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_night_light.h b/light/color_night_light.h index 1c48d7a..4af8f3d 100644 --- a/light/color_night_light.h +++ b/light/color_night_light.h @@ -1,8 +1,8 @@ #pragma once #include "../common.h" -#include "light_modes.h" #include "gpio_outputs.h" +#include "light_modes.h" namespace esphome { namespace xiaomi { @@ -23,42 +23,41 @@ namespace bslamp2 { * device's yaml configuration. */ class ColorNightLight : public GPIOOutputs { -public: - bool set_light_color_values(light::LightColorValues v) { - light_mode = LIGHT_MODE_NIGHT; + public: + bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_NIGHT; - // Note: I do not check for a brightness at or below 0.01 (1%) here, - // because the lowest brightness setting from Home Assistant turns - // up as 0.011765 in here (which is 3/255 and not 1/100). - if (v.get_brightness() >= 0.012f) { - return false; - } + // Note: I do not check for a brightness at or below 0.01 (1%) here, + // because the lowest brightness setting from Home Assistant turns + // up as 0.011765 in here (which is 3/255 and not 1/100). + if (v.get_brightness() >= 0.012f) + return false; - // This night light mode is activated when white light is selected. - // Based on measurements using the original device firmware, so it - // matches the night light of the original firmware. - if (v.get_white() > 0) { - red = 0.968f; - green = 0.968f; - blue = 0.972f; - white = 0.0f; - } - // In RGB mode, the selected color is used to give the night light a - // specific color, instead of the default. This is a nice extra for - // this firmware, as the original firmware does not support it. - else { - red = esphome::lerp(v.get_red(), 0.9997f, 0.9680f); - green = esphome::lerp(v.get_green(), 0.9997f, 0.9680f); - auto blue_scale = (v.get_red() + v.get_green()) / 2.0f; - auto blue_max = esphome::lerp(blue_scale, 0.9640f, 0.9720f); - blue = esphome::lerp(v.get_blue(), 0.9997f, blue_max); - white = 0.0f; - } - - return true; + // This night light mode is activated when white light is selected. + // Based on measurements using the original device firmware, so it + // matches the night light of the original firmware. + if (v.get_white() > 0) { + red = 0.968f; + green = 0.968f; + blue = 0.972f; + white = 0.0f; + } + // In RGB mode, the selected color is used to give the night light a + // specific color, instead of the default. This is a nice extra for + // this firmware, as the original firmware does not support it. + else { + red = esphome::lerp(v.get_red(), 0.9997f, 0.9680f); + green = esphome::lerp(v.get_green(), 0.9997f, 0.9680f); + auto blue_scale = (v.get_red() + v.get_green()) / 2.0f; + auto blue_max = esphome::lerp(blue_scale, 0.9640f, 0.9720f); + blue = esphome::lerp(v.get_blue(), 0.9997f, blue_max); + white = 0.0f; } + + return true; + } }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_off.h b/light/color_off.h index 8ffa236..cd97b5d 100644 --- a/light/color_off.h +++ b/light/color_off.h @@ -1,8 +1,8 @@ #pragma once #include "../common.h" -#include "light_modes.h" #include "gpio_outputs.h" +#include "light_modes.h" namespace esphome { namespace xiaomi { @@ -12,23 +12,22 @@ namespace bslamp2 { * This class can handle the GPIO outputs in case the light of turned off. */ class ColorOff : public GPIOOutputs { -public: - bool set_light_color_values(light::LightColorValues v) { - light_mode = LIGHT_MODE_OFF; + public: + bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_OFF; - if (v.get_state() != 0.0f && v.get_brightness() != 0.0f) { - return false; - } + if (v.get_state() != 0.0f && v.get_brightness() != 0.0f) + return false; - red = 1.0f; - green = 1.0f; - blue = 1.0f; - white = 0.0f; + red = 1.0f; + green = 1.0f; + blue = 1.0f; + white = 0.0f; - return true; - } + return true; + } }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_rgb_light.h b/light/color_rgb_light.h index b6857a5..1a6e880 100644 --- a/light/color_rgb_light.h +++ b/light/color_rgb_light.h @@ -4,22 +4,22 @@ #include #include "../common.h" -#include "light_modes.h" #include "gpio_outputs.h" +#include "light_modes.h" namespace esphome { namespace xiaomi { namespace bslamp2 { struct RGB { - float red; - float green; - float blue; + float red; + float green; + float blue; }; struct RGBPoint { - RGB low; - RGB high; + RGB low; + RGB high; }; using RGBRing = std::array; @@ -27,8 +27,8 @@ using RGBRing = std::array; using RGBCircle = std::array; /** - * The following table contains GPIO PWM duty cycles as used for driving the - * LEDs in the device in RGB mode. + * The following table contains GPIO PWM duty cycles as used for driving + * the LEDs in the device in RGB mode. * * The base for this table are measurements against the original device * firmware, using the RGB color circle as used in Home Assistant as the @@ -49,6 +49,7 @@ using RGBCircle = std::array; * in-between brightnesses can be derived from these values by means of * linear interpolation. */ +// clang-format off static const RGBCircle rgb_circle_ {{ // Ring 0, min value RGB component value = 0 {{ @@ -240,144 +241,138 @@ static const RGBCircle rgb_circle_ {{ {{ 0.9167, 0.8727, 0.9360 }, { 0.4406, 0.0000, 0.6330 }} // 345° [255,228,219] }} }}; +// clang-format on /** * This class can handle the GPIO outputs for the RGB light mode, * based on RGB color values + brightness. */ class ColorRGBLight : public GPIOOutputs { -public: - bool set_light_color_values(light::LightColorValues v) { - light_mode = LIGHT_MODE_RGB; + public: + bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_RGB; - if (v.get_white() > 0.0f) { - return false; - } - - // Determine the ring level for the color. This is a value between 0 - // and 7, determining in what ring of the RGB circle the requested - // color resides. - auto rgb_min = min(min(v.get_red(), v.get_green()), v.get_blue()); - auto level = 7.0f * rgb_min; + if (v.get_white() > 0.0f) { + return false; + } - // While the default color circle in Home Assistant presents only a - // subset of colors, it is possible to request colors outside this - // subset as well. Therefore, the ring level might contain a - // fractional value instead of a plain integer. To accomodate for - // this, interpolation will be done to get the final outputs. + // Determine the ring level for the color. This is a value between 0 + // and 7, determining in what ring of the RGB circle the requested + // color resides. + auto rgb_min = min(min(v.get_red(), v.get_green()), v.get_blue()); + auto level = 7.0f * rgb_min; - // Determine duty cycle measurements for the outer ring. - auto level_a = floor(level); - set_duty_cycles_( - &rgbp_a_, level_a, v.get_red(), v.get_green(), v.get_blue(), - v.get_brightness(), &rgb_a_); + // While the default color circle in Home Assistant presents only a + // subset of colors, it is possible to request colors outside this + // subset as well. Therefore, the ring level might contain a + // fractional value instead of a plain integer. To accomodate for + // this, interpolation will be done to get the final outputs. - // Determine duty cycle measurements for the inner ring. - set_duty_cycles_( - &rgbp_b_, level_a, v.get_red(), v.get_green(), v.get_blue(), - v.get_brightness(), &rgb_b_); + // Determine duty cycle measurements for the outer ring. + auto level_a = floor(level); + set_duty_cycles_(&rgbp_a_, level_a, v.get_red(), v.get_green(), v.get_blue(), v.get_brightness(), &rgb_a_); - // Almost there! We now have the correct duty cycles for the - // two rings that we were looking at. In this last step, the - // two values are interpolated based on the ring level. - auto d = level - level_a; - red = esphome::lerp(d, rgb_a_.red, rgb_b_.red); - green = esphome::lerp(d, rgb_a_.green, rgb_b_.green); - blue = esphome::lerp(d, rgb_a_.blue, rgb_b_.blue); + // Determine duty cycle measurements for the inner ring. + set_duty_cycles_(&rgbp_b_, level_a, v.get_red(), v.get_green(), v.get_blue(), v.get_brightness(), &rgb_b_); - // The white output channel will always be 0 for RGB. - white = 0.0f; + // Almost there! We now have the correct duty cycles for the + // two rings that we were looking at. In this last step, the + // two values are interpolated based on the ring level. + auto d = level - level_a; + red = esphome::lerp(d, rgb_a_.red, rgb_b_.red); + green = esphome::lerp(d, rgb_a_.green, rgb_b_.green); + blue = esphome::lerp(d, rgb_a_.blue, rgb_b_.blue); - return true; - } + // The white output channel will always be 0 for RGB. + white = 0.0f; - RGBPoint rgbp_a_; - RGBPoint rgbp_b_; - RGB rgb_a_; - RGB rgb_b_; + return true; + } - void set_duty_cycles_(RGBPoint *p, int ring_level, - float r, float g, float b, float brightness, RGB *rgb) { + RGBPoint rgbp_a_; + RGBPoint rgbp_b_; + RGB rgb_a_; + RGB rgb_b_; - // Ring level 7 = white light center. The duty cycles for this level - // can be computed using a few basic functions. - if (ring_level == 7) { - rgb->red = 0.932101 - 0.383377 * brightness; - rgb->green = 0.883185 - 0.881623 * brightness; - rgb->blue = 0.94188 - 0.284498 * brightness; - return; - } + void set_duty_cycles_(RGBPoint *p, int ring_level, float r, float g, float b, float brightness, RGB *rgb) { + // Ring level 7 = white light center. The duty cycles for this level + // can be computed using a few basic functions. + if (ring_level == 7) { + rgb->red = 0.932101 - 0.383377 * brightness; + rgb->green = 0.883185 - 0.881623 * brightness; + rgb->blue = 0.94188 - 0.284498 * brightness; + return; + } - // Other ring levels are more complex. Start by retrieving the duty - // cycle measurement data for the ring at hand. - auto ring = rgb_circle_[ring_level]; + // Other ring levels are more complex. Start by retrieving the duty + // cycle measurement data for the ring at hand. + auto ring = rgb_circle_[ring_level]; - // Because we only have a subset of all colors in the RGB ring - // available in the configuration table, some interpolation will - // have to be done. - // First, compute the position on the ring for the requested RGB - // color. This is basically a hue representation of the requested - // color. It is expressed as a number of degrees around the ring, - // starting with red (at 0°). - auto pos = ring_pos_(r, g, b) / 15.0f; + // Because we only have a subset of all colors in the RGB ring + // available in the configuration table, some interpolation will + // have to be done. + // First, compute the position on the ring for the requested RGB + // color. This is basically a hue representation of the requested + // color. It is expressed as a number of degrees around the ring, + // starting with red (at 0°). + auto pos = ring_pos_(r, g, b) / 15.0f; - // Since there are 24 measurements for each ring, each measurement - // covers 360°/24 = 15°. Using that knowledge, the measurements to - // use for interpolation can be picked from the ring data. - auto pos_x = floor(pos); - auto x = ring[pos_x]; - auto pos_y = ceil(pos); - auto y = ring[pos_y > 23 ? 0 : pos_y]; + // Since there are 24 measurements for each ring, each measurement + // covers 360°/24 = 15°. Using that knowledge, the measurements to + // use for interpolation can be picked from the ring data. + auto pos_x = floor(pos); + auto x = ring[pos_x]; + auto pos_y = ceil(pos); + auto y = ring[pos_y > 23 ? 0 : pos_y]; - // Interpolate based on the ring position. - auto d = pos - pos_x; - p->low.red = esphome::lerp(d, x.low.red, y.low.red); - p->low.green = esphome::lerp(d, x.low.green, y.low.green); - p->low.blue = esphome::lerp(d, x.low.blue, y.low.blue); - p->high.red = esphome::lerp(d, x.high.red, y.high.red); - p->high.green = esphome::lerp(d, x.high.green, y.high.green); - p->high.blue = esphome::lerp(d, x.high.blue, y.high.blue); + // Interpolate based on the ring position. + auto d = pos - pos_x; + p->low.red = esphome::lerp(d, x.low.red, y.low.red); + p->low.green = esphome::lerp(d, x.low.green, y.low.green); + p->low.blue = esphome::lerp(d, x.low.blue, y.low.blue); + p->high.red = esphome::lerp(d, x.high.red, y.high.red); + p->high.green = esphome::lerp(d, x.high.green, y.high.green); + p->high.blue = esphome::lerp(d, x.high.blue, y.high.blue); - // Interpolate based on brightness level. - apply_brightness_(p, brightness, rgb); - } + // Interpolate based on brightness level. + apply_brightness_(p, brightness, rgb); + } -protected: - /** - * Returns the position on an RGB ring in degrees (0 - 359). - */ - float ring_pos_(float red, float green, float blue) { - auto rgb_min = min(min(red, green), blue); - auto rgb_max = max(max(red, green), blue); - auto delta = rgb_max - rgb_min; - float pos; - if (delta == 0.0f) - pos = 0.0f; - else if (red == rgb_max) - pos = 60.0f * fmod((green - blue) / delta, 6); - else if (green == rgb_max) - pos = 60.0f * ((blue - red) / delta + 2.0f); - else - pos = 60.0f * ((red - green) / delta + 4.0f); - if (pos < 0) - pos = pos + 360; - return pos; - } + protected: + /** + * Returns the position on an RGB ring in degrees (0 - 359). + */ + float ring_pos_(float red, float green, float blue) { + auto rgb_min = min(min(red, green), blue); + auto rgb_max = max(max(red, green), blue); + auto delta = rgb_max - rgb_min; + float pos; + if (delta == 0.0f) + pos = 0.0f; + else if (red == rgb_max) + pos = 60.0f * fmod((green - blue) / delta, 6); + else if (green == rgb_max) + pos = 60.0f * ((blue - red) / delta + 2.0f); + else + pos = 60.0f * ((red - green) / delta + 4.0f); + if (pos < 0) + pos = pos + 360; + return pos; + } - /** - * Apply brightness interpolation to the duty cycle measurements. We - * have the low (0.01) and high (1.00) brightness measurements in the - * data. Brightness can be applied by means of linear interpolation. - */ - void apply_brightness_(RGBPoint *p, float brightness, RGB *rgb) { - auto d = brightness - 0.01f; - rgb->red = esphome::lerp(d, p->low.red, p->high.red); - rgb->green = esphome::lerp(d, p->low.green, p->high.green); - rgb->blue = esphome::lerp(d, p->low.blue, p->high.blue); - } + /** + * Apply brightness interpolation to the duty cycle measurements. We + * have the low (0.01) and high (1.00) brightness measurements in the + * data. Brightness can be applied by means of linear interpolation. + */ + void apply_brightness_(RGBPoint *p, float brightness, RGB *rgb) { + auto d = brightness - 0.01f; + rgb->red = esphome::lerp(d, p->low.red, p->high.red); + rgb->green = esphome::lerp(d, p->low.green, p->high.green); + rgb->blue = esphome::lerp(d, p->low.blue, p->high.blue); + } }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_transition_handler.h b/light/color_transition_handler.h index 0c0184b..236eb7b 100644 --- a/light/color_transition_handler.h +++ b/light/color_transition_handler.h @@ -1,8 +1,8 @@ #pragma once #include "../common.h" -#include "gpio_outputs.h" #include "color_instant_handler.h" +#include "gpio_outputs.h" namespace esphome { namespace xiaomi { @@ -17,142 +17,135 @@ namespace bslamp2 { * check whether or not a light color transition is in progress. */ class LightStateTransformerInspector { -public: - virtual bool is_active() = 0; - virtual bool is_transition() = 0; - virtual light::LightColorValues get_end_values() = 0; - virtual float get_progress() = 0; + public: + virtual bool is_active() = 0; + virtual bool is_transition() = 0; + virtual light::LightColorValues get_end_values() = 0; + virtual float get_progress() = 0; }; /** * This class is used to handle specific light color transition requirements * for the device. - * + * * When using the default ESPHome logic, transitioning is done by * transitioning all light properties linearly from the original values to * the new values, and letting the light output object translate these * properties into light outputs on every step of the way. While this does * work, it does not work nicely. - * + * * For example, when transitioning from warm to cold white light, the color * temperature would be transitioned from the old value to the new value. * While doing so, the transition hits the middle white light setting, which * shows up as a bright flash in the middle of the transition. The original * firmware however, shows a smooth transition from warm to cold white * light, without any flash. - * + * * This class handles transitions by not varying the light properties over * time, but by transitioning the LEDC duty cycle output levels over time. * This matches the behavior of the original firmware. */ class ColorTransitionHandler : public GPIOOutputs { -public: - ColorTransitionHandler(LightStateTransformerInspector *inspector) : transformer_(inspector) {} - - light::LightColorValues get_end_values() { - return end_light_values_; + public: + ColorTransitionHandler(LightStateTransformerInspector *inspector) : transformer_(inspector) {} + + light::LightColorValues get_end_values() { return end_light_values_; } + + bool set_light_color_values(light::LightColorValues values) { + if (!light_state_has_active_transition_()) { + // Remember the last active light color values. When a transition + // is detected, we'll use these as the starting point. It is not + // possible to use the current values at that point, because the + // transition is already in progress by the time the transition + // is detected. + start_light_values_ = values; + + active_ = false; + return false; } - bool set_light_color_values(light::LightColorValues values) { - if (!light_state_has_active_transition_()) { - // Remember the last active light color values. When a transition - // is detected, we'll use these as the starting point. It is not - // possible to use the current values at that point, because the - // transition is already in progress by the time the transition - // is detected. - start_light_values_ = values; - - active_ = false; - return false; - } - - // When a fresh transition is started, then compute the GPIO outputs - // to use for both the start and end point. This transition handler - // will then transition linearly between these two. - if (is_fresh_transition_()) { - start_->set_light_color_values(start_light_values_); - end_light_values_ = transformer_->get_end_values(); - end_->set_light_color_values(end_light_values_); - active_ = true; - } - // When a transition is modified, then use the current GPIO outputs - // as the new starting point. - else if (is_modified_transition_()) { - this->copy_to(start_); - end_light_values_ = transformer_->get_end_values(); - end_->set_light_color_values(end_light_values_); - } - - light_mode = end_->light_mode; - progress_ = transformer_->get_progress(); - - // Determine required GPIO outputs for current transition progress. - - // In night light mode, do not use actual transitions. Transitioning - // between colors at the very low LED output levels of the night light, - // results in light drops, which are plain ugly to watch. - if (light_mode == "night") { - red = end_->red; - green = end_->green; - blue = end_->blue; - white = end_->white; - } - // In other light modes, apply smooth transitioning. - else { - auto smoothed = light::LightTransitionTransformer::smoothed_progress(progress_); - red = esphome::lerp(smoothed, start_->red, end_->red); - green = esphome::lerp(smoothed, start_->green, end_->green); - blue = esphome::lerp(smoothed, start_->blue, end_->blue); - white = esphome::lerp(smoothed, start_->white, end_->white); - } - - return true; + // When a fresh transition is started, then compute the GPIO outputs + // to use for both the start and end point. This transition handler + // will then transition linearly between these two. + if (is_fresh_transition_()) { + start_->set_light_color_values(start_light_values_); + end_light_values_ = transformer_->get_end_values(); + end_->set_light_color_values(end_light_values_); + active_ = true; } - -protected: - bool active_ = false; - float progress_ = 0.0f; - LightStateTransformerInspector *transformer_; - light::LightColorValues start_light_values_; - light::LightColorValues end_light_values_; - GPIOOutputs *start_ = new ColorInstantHandler(); - GPIOOutputs *end_ = new ColorInstantHandler(); - - /** - * Checks if the LightState currently has an active LightTransformer. - */ - bool light_state_has_active_transition_() { - if (!transformer_->is_active()) - return false; - if (!transformer_->is_transition()) - return false; - return true; + // When a transition is modified, then use the current GPIO outputs + // as the new starting point. + else if (is_modified_transition_()) { + this->copy_to(start_); + end_light_values_ = transformer_->get_end_values(); + end_->set_light_color_values(end_light_values_); } - /** - * Checks if a fresh transitioning is started. - * A transitioning is fresh when no existing transition is active. - */ - bool is_fresh_transition_() { - return active_ == false; - } + light_mode = end_->light_mode; + progress_ = transformer_->get_progress(); + + // Determine required GPIO outputs for current transition progress. - /** - * Checks if a new end state is set, while an existing transition - * is active. This might be detected in two ways: - * - the end color has been updated - * - the progress has been reverted - */ - bool is_modified_transition_() { - auto new_end_light_values = transformer_->get_end_values(); - auto new_progress = transformer_->get_progress(); - return ( - new_end_light_values != end_light_values_ || - new_progress < progress_ - ); + // In night light mode, do not use actual transitions. Transitioning + // between colors at the very low LED output levels of the night light, + // results in light drops, which are plain ugly to watch. + if (light_mode == "night") { + red = end_->red; + green = end_->green; + blue = end_->blue; + white = end_->white; } + // In other light modes, apply smooth transitioning. + else { + auto smoothed = light::LightTransitionTransformer::smoothed_progress(progress_); + red = esphome::lerp(smoothed, start_->red, end_->red); + green = esphome::lerp(smoothed, start_->green, end_->green); + blue = esphome::lerp(smoothed, start_->blue, end_->blue); + white = esphome::lerp(smoothed, start_->white, end_->white); + } + + return true; + } + + protected: + bool active_ = false; + float progress_ = 0.0f; + LightStateTransformerInspector *transformer_; + light::LightColorValues start_light_values_; + light::LightColorValues end_light_values_; + GPIOOutputs *start_ = new ColorInstantHandler(); + GPIOOutputs *end_ = new ColorInstantHandler(); + + /** + * Checks if the LightState currently has an active LightTransformer. + */ + bool light_state_has_active_transition_() { + if (!transformer_->is_active()) + return false; + if (!transformer_->is_transition()) + return false; + return true; + } + + /** + * Checks if a fresh transitioning is started. + * A transitioning is fresh when no existing transition is active. + */ + bool is_fresh_transition_() { return active_ == false; } + + /** + * Checks if a new end state is set, while an existing transition + * is active. This might be detected in two ways: + * - the end color has been updated + * - the progress has been reverted + */ + bool is_modified_transition_() { + auto new_end_light_values = transformer_->get_end_values(); + auto new_progress = transformer_->get_progress(); + return (new_end_light_values != end_light_values_ || new_progress < progress_); + } }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/color_white_light.h b/light/color_white_light.h index 5042be7..6360b9d 100644 --- a/light/color_white_light.h +++ b/light/color_white_light.h @@ -4,7 +4,7 @@ #include #include "../common.h" -#include "light_modes.h" +#include "light_modes.h" #include "gpio_outputs.h" namespace esphome { @@ -24,15 +24,16 @@ static const int MIRED_MIN = 153; static const int MIRED_MAX = 588; struct RGBWLevelsByTemperature { - float from_temperature; - float red; - float green; - float blue; - float white; + float from_temperature; + float red; + float green; + float blue; + float white; }; using RGBWLevelsTable = std::array; +// clang-format off static const RGBWLevelsTable rgbw_levels_1_ {{ { 501.0f, 0.873f, 0.907f, 1.000f, 0.063f }, { 455.0f, 0.873f, 0.896f, 1.000f, 0.063f }, @@ -68,62 +69,60 @@ static const RGBWLevelsTable rgbw_levels_100_ {{ { 154.0f, 1.000f, 0.000f, 0.218f, 0.368f }, { 153.0f, 1.000f, 0.000f, 0.187f, 0.335f } }}; +// clang-format on /** * This class can handle the GPIO outputs for the white light mode, * based on color temperature + brightness. */ class ColorWhiteLight : public GPIOOutputs { -public: - bool set_light_color_values(light::LightColorValues v) { - light_mode = LIGHT_MODE_WHITE; + public: + bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_WHITE; - if (v.get_white() == 0.0f) { - return false; - } - - auto temperature = clamp_temperature_(v.get_color_temperature()); - auto brightness = clamp_brightness_(v.get_brightness()); - - auto levels_1 = lookup_in_table_(rgbw_levels_1_, temperature); - auto levels_100 = lookup_in_table_(rgbw_levels_100_, temperature); - - red = esphome::lerp(brightness, levels_1.red, levels_100.red); - green = esphome::lerp(brightness, levels_1.green, levels_100.green); - blue = esphome::lerp(brightness, levels_1.blue, levels_100.blue); - white = esphome::lerp(brightness, levels_1.white, levels_100.white); - - return true; + if (v.get_white() == 0.0f) { + return false; } -protected: - float clamp_temperature_(float temperature) - { - if (temperature > MIRED_MAX) - temperature = MIRED_MAX; - else if (temperature < MIRED_MIN) - temperature = MIRED_MIN; - return temperature; - } - - float clamp_brightness_(float brightness) - { - if (brightness < 0.01f) - brightness = 0.01f; - else if (brightness > 1.00f) - brightness = 1.00f; - return brightness; - } - - RGBWLevelsByTemperature lookup_in_table_(RGBWLevelsTable table, float temperature) - { - for (RGBWLevelsByTemperature& item : table) - if (temperature >= item.from_temperature) - return item; - throw std::invalid_argument("received too low temperature"); - } + auto temperature = clamp_temperature_(v.get_color_temperature()); + auto brightness = clamp_brightness_(v.get_brightness()); + + auto levels_1 = lookup_in_table_(rgbw_levels_1_, temperature); + auto levels_100 = lookup_in_table_(rgbw_levels_100_, temperature); + + red = esphome::lerp(brightness, levels_1.red, levels_100.red); + green = esphome::lerp(brightness, levels_1.green, levels_100.green); + blue = esphome::lerp(brightness, levels_1.blue, levels_100.blue); + white = esphome::lerp(brightness, levels_1.white, levels_100.white); + + return true; + } + + protected: + float clamp_temperature_(float temperature) { + if (temperature > MIRED_MAX) + temperature = MIRED_MAX; + else if (temperature < MIRED_MIN) + temperature = MIRED_MIN; + return temperature; + } + + float clamp_brightness_(float brightness) { + if (brightness < 0.01f) + brightness = 0.01f; + else if (brightness > 1.00f) + brightness = 1.00f; + return brightness; + } + + RGBWLevelsByTemperature lookup_in_table_(RGBWLevelsTable table, float temperature) { + for (RGBWLevelsByTemperature& item : table) + if (temperature >= item.from_temperature) + return item; + throw std::invalid_argument("received too low temperature"); + } }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/gpio_outputs.h b/light/gpio_outputs.h index f8c7877..555dea7 100644 --- a/light/gpio_outputs.h +++ b/light/gpio_outputs.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "light_modes.h" @@ -12,39 +12,36 @@ namespace bslamp2 { * the requested color on the physical device. */ class GPIOOutputs { -public: - float red = 0.0f; - float green = 0.0f; - float blue = 0.0f; - float white = 0.0f; - std::string light_mode = LIGHT_MODE_OFF; + public: + float red = 0.0f; + float green = 0.0f; + float blue = 0.0f; + float white = 0.0f; + std::string light_mode = LIGHT_MODE_OFF; - /** - * Sets the red, green, blue, white fields to the PWM duty cycles - * that are required to represent the requested light color for - * the provided LightColorValues input. - * - * Returns true when the input can be handled, false otherwise. - */ - virtual bool set_light_color_values(light::LightColorValues v) = 0; + /** + * Sets the red, green, blue, white fields to the PWM duty cycles + * that are required to represent the requested light color for + * the provided LightColorValues input. + * + * Returns true when the input can be handled, false otherwise. + */ + virtual bool set_light_color_values(light::LightColorValues v) = 0; - /** - * Copies the current output values to another GPIOOutputs object. - */ - void copy_to(GPIOOutputs *other) { - other->red = red; - other->green = green; - other->blue = blue; - other->white = white; - other->light_mode = light_mode; - } + /** + * Copies the current output values to another GPIOOutputs object. + */ + void copy_to(GPIOOutputs *other) { + other->red = red; + other->green = green; + other->blue = blue; + other->white = white; + other->light_mode = light_mode; + } - void log(const char *prefix) { - ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f", - prefix, red, green, blue, white); - } + void log(const char *prefix) { ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f", prefix, red, green, blue, white); } }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/light_modes.h b/light/light_modes.h index 6df31a1..2111d95 100644 --- a/light/light_modes.h +++ b/light/light_modes.h @@ -1,15 +1,15 @@ -#pragma once +#pragma once namespace esphome { namespace xiaomi { namespace bslamp2 { -static const std::string LIGHT_MODE_UNKNOWN { "unknown" }; -static const std::string LIGHT_MODE_OFF { "off" }; -static const std::string LIGHT_MODE_RGB { "rgb" }; -static const std::string LIGHT_MODE_WHITE { "white" }; -static const std::string LIGHT_MODE_NIGHT { "night" }; +static const std::string LIGHT_MODE_UNKNOWN{"unknown"}; +static const std::string LIGHT_MODE_OFF{"off"}; +static const std::string LIGHT_MODE_RGB{"rgb"}; +static const std::string LIGHT_MODE_WHITE{"white"}; +static const std::string LIGHT_MODE_NIGHT{"night"}; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/light_output.h b/light/light_output.h index 0c4afd0..0b53da0 100644 --- a/light/light_output.h +++ b/light/light_output.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../common.h" #include "../light_hal.h" @@ -19,96 +19,89 @@ namespace bslamp2 { * logical light color input. */ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { -public: - void set_parent(LightHAL *light) { light_ = light; } + public: + void set_parent(LightHAL *light) { light_ = light; } - /** - * Returns a LightTraits object, which is used to explain to the outside - * world (e.g. Home Assistant) what features are supported by this device. - */ - light::LightTraits get_traits() override - { - auto traits = light::LightTraits(); - traits.set_supports_rgb(true); - traits.set_supports_color_temperature(true); - traits.set_supports_brightness(true); - traits.set_supports_rgb_white_value(false); - traits.set_supports_color_interlock(true); - traits.set_min_mireds(MIRED_MIN); - traits.set_max_mireds(MIRED_MAX); - return traits; - } + /** + * Returns a LightTraits object, which is used to explain to the outside + * world (e.g. Home Assistant) what features are supported by this device. + */ + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supports_rgb(true); + traits.set_supports_color_temperature(true); + traits.set_supports_brightness(true); + traits.set_supports_rgb_white_value(false); + traits.set_supports_color_interlock(true); + traits.set_min_mireds(MIRED_MIN); + traits.set_max_mireds(MIRED_MAX); + return traits; + } - void add_on_light_mode_callback(std::function &&callback) { - light_mode_callback_.add(std::move(callback)); - } + void add_on_light_mode_callback(std::function &&callback) { + light_mode_callback_.add(std::move(callback)); + } - void add_on_state_callback(std::function &&callback) { - state_callback_.add(std::move(callback)); - } + void add_on_state_callback(std::function &&callback) { + state_callback_.add(std::move(callback)); + } - /** - * Applies a requested light state to the physicial GPIO outputs. - */ - void write_state(light::LightState *state) - { - auto values = state->current_values; + /** + * Applies a requested light state to the physicial GPIO outputs. + */ + void write_state(light::LightState *state) { + auto values = state->current_values; - // The color must either be set instantly, or the color is - // transitioning to an end color. The transition handler will do its - // own inspection to see if a transition is currently active or not. - // Based on the outcome, use either the instant or transition handler. - GPIOOutputs *delegate; - if (transition_handler_->set_light_color_values(values)) { - delegate = transition_handler_; - light_mode_callback_.call(delegate->light_mode); - state_callback_.call(transition_handler_->get_end_values()); - } else { - instant_handler_->set_light_color_values(values); - delegate = instant_handler_; - light_mode_callback_.call(delegate->light_mode); - state_callback_.call(values); - } + // The color must either be set instantly, or the color is + // transitioning to an end color. The transition handler will do its + // own inspection to see if a transition is currently active or not. + // Based on the outcome, use either the instant or transition handler. + GPIOOutputs *delegate; + if (transition_handler_->set_light_color_values(values)) { + delegate = transition_handler_; + light_mode_callback_.call(delegate->light_mode); + state_callback_.call(transition_handler_->get_end_values()); + } else { + instant_handler_->set_light_color_values(values); + delegate = instant_handler_; + light_mode_callback_.call(delegate->light_mode); + state_callback_.call(values); + } - // Note: one might think that it is more logical to turn on the LED - // circuitry master switch after setting the individual channels, - // but this is the order that was used by the original firmware. I - // tried to stay as close as possible to the original behavior, so - // that's why these GPIOs are turned on at this point. - if (values.get_state() != 0) - light_->turn_on(); + // Note: one might think that it is more logical to turn on the LED + // circuitry master switch after setting the individual channels, + // but this is the order that was used by the original firmware. I + // tried to stay as close as possible to the original behavior, so + // that's why these GPIOs are turned on at this point. + if (values.get_state() != 0) + light_->turn_on(); - // Apply the current GPIO output levels from the selected handler. - light_->set_rgbw( - delegate->red, - delegate->green, - delegate->blue, - delegate->white - ); + // Apply the current GPIO output levels from the selected handler. + light_->set_rgbw(delegate->red, delegate->green, delegate->blue, delegate->white); - if (values.get_state() == 0) - light_->turn_off(); - } + if (values.get_state() == 0) + light_->turn_off(); + } -protected: - LightHAL *light_; - ColorTransitionHandler *transition_handler_; - ColorInstantHandler *instant_handler_ = new ColorInstantHandler(); - CallbackManager light_mode_callback_{}; - CallbackManager state_callback_{}; + protected: + LightHAL *light_; + ColorTransitionHandler *transition_handler_; + ColorInstantHandler *instant_handler_ = new ColorInstantHandler(); + CallbackManager light_mode_callback_{}; + CallbackManager state_callback_{}; - friend class XiaomiBslamp2LightState; + friend class XiaomiBslamp2LightState; - /** - * Called by the XiaomiBslamp2LightState class, to set the object that can be - * used to access the protected LightTransformer data from the LightState - * object. - */ - void set_transformer_inspector(LightStateTransformerInspector *exposer) { - transition_handler_ = new ColorTransitionHandler(exposer); - } + /** + * Called by the XiaomiBslamp2LightState class, to set the object that can be + * used to access the protected LightTransformer data from the LightState + * object. + */ + void set_transformer_inspector(LightStateTransformerInspector *exposer) { + transition_handler_ = new ColorTransitionHandler(exposer); + } }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/light_state.h b/light/light_state.h index 06d72ca..3471a80 100644 --- a/light/light_state.h +++ b/light/light_state.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../common.h" @@ -13,19 +13,18 @@ namespace bslamp2 { * This class is used by the ColorTransitionHandler class to inspect if * an ongoing light color transition is active in the LightState object. */ -class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector -{ -public: - XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { - output->set_transformer_inspector(this); - } +class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector { + public: + XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { + output->set_transformer_inspector(this); + } - bool is_active() { return transformer_ != nullptr; } - bool is_transition() { return transformer_->is_transition(); } - light::LightColorValues get_end_values() { return transformer_->get_end_values(); } - float get_progress() { return transformer_->get_progress(); } + bool is_active() { return transformer_ != nullptr; } + bool is_transition() { return transformer_->is_transition(); } + light::LightColorValues get_end_values() { return transformer_->get_end_values(); } + float get_progress() { return transformer_->get_progress(); } }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light/presets.h b/light/presets.h index bd6c95c..f42c3d4 100644 --- a/light/presets.h +++ b/light/presets.h @@ -8,203 +8,183 @@ namespace xiaomi { namespace bslamp2 { class Preset : public Component { -public: - std::string group_name; - std::string name; - Preset *next_preset = nullptr; - - explicit Preset(light::LightState *light, std::string group_name, std::string name): - group_name(group_name), name(name), light_state_(light) {} - - void set_transition_length(uint32_t t) { transition_length_ = t; } - void set_brightness(float t) { brightness_ = t; } - void set_red(float t) { red_ = t; } - void set_green(float t) { green_ = t; } - void set_blue(float t) { blue_ = t; } - void set_color_temperature(float t) { color_temperature_ = t; } - void set_effect(const std::string &effect) { effect_ = effect; } - - void apply() { - ESP_LOGI(TAG, "Activating light preset: %s/%s", - group_name.c_str(), name.c_str()); - auto call = light_state_->make_call(); - call.set_state(true); - if (transition_length_.has_value()) { - call.set_transition_length(*transition_length_); - } - if (brightness_.has_value()) { - call.set_brightness(*brightness_); - } - if (red_.has_value()) { - call.set_red(*red_); - } - if (green_.has_value()) { - call.set_green(*green_); - } - if (blue_.has_value()) { - call.set_blue(*blue_); - } - if (color_temperature_.has_value()) { - call.set_color_temperature(*color_temperature_); - } - if (effect_.has_value()) { - call.set_effect(*effect_); - } - call.perform(); - } - -protected: - light::LightState *light_state_; - optional transition_length_; - optional brightness_; - optional red_; - optional green_; - optional blue_; - optional color_temperature_; - optional effect_; + public: + std::string group_name; + std::string name; + Preset *next_preset = nullptr; + + explicit Preset(light::LightState *light, std::string group_name, std::string name) + : group_name(group_name), name(name), light_state_(light) {} + + void set_transition_length(uint32_t t) { transition_length_ = t; } + void set_brightness(float t) { brightness_ = t; } + void set_red(float t) { red_ = t; } + void set_green(float t) { green_ = t; } + void set_blue(float t) { blue_ = t; } + void set_color_temperature(float t) { color_temperature_ = t; } + void set_effect(const std::string &effect) { effect_ = effect; } + + void apply() { + ESP_LOGI(TAG, "Activating light preset: %s/%s", group_name.c_str(), name.c_str()); + auto call = light_state_->make_call(); + call.set_state(true); + if (transition_length_.has_value()) + call.set_transition_length(*transition_length_); + if (brightness_.has_value()) + call.set_brightness(*brightness_); + if (red_.has_value()) + call.set_red(*red_); + if (green_.has_value()) + call.set_green(*green_); + if (blue_.has_value()) + call.set_blue(*blue_); + if (color_temperature_.has_value()) + call.set_color_temperature(*color_temperature_); + if (effect_.has_value()) + call.set_effect(*effect_); + call.perform(); + } + + protected: + light::LightState *light_state_; + optional transition_length_; + optional brightness_; + optional red_; + optional green_; + optional blue_; + optional color_temperature_; + optional effect_; }; class PresetGroup { -public: - std::string name; - PresetGroup *next_group = nullptr; - Preset *first_preset = nullptr; - Preset *last_preset = nullptr; - Preset *active_preset = nullptr; - - explicit PresetGroup(std::string g_name) : name(g_name) {} - - void add_preset(Preset *p) { - if (first_preset == nullptr) { - first_preset = last_preset = active_preset = p; - } else { - last_preset->next_preset = p; - last_preset = p; - } - } - - Preset *get_preset(std::string p_name) { - for (auto p = first_preset; p != nullptr; p = p->next_preset) { - if (p->name == p_name) { - return p; - } - } - return nullptr; + public: + std::string name; + PresetGroup *next_group = nullptr; + Preset *first_preset = nullptr; + Preset *last_preset = nullptr; + Preset *active_preset = nullptr; + + explicit PresetGroup(std::string g_name) : name(g_name) {} + + void add_preset(Preset *p) { + if (first_preset == nullptr) { + first_preset = last_preset = active_preset = p; + } else { + last_preset->next_preset = p; + last_preset = p; } + } + + Preset *get_preset(std::string p_name) { + for (auto p = first_preset; p != nullptr; p = p->next_preset) + if (p->name == p_name) + return p; + return nullptr; + } }; class PresetsContainer : public Component { -public: - PresetGroup *first_group = nullptr; - PresetGroup *last_group = nullptr; - PresetGroup *active_group = nullptr; - - void dump_config() { - if (first_group == nullptr) { - return; - } - ESP_LOGCONFIG(TAG, "Light Presets:"); - for (auto g = first_group; g != nullptr; g = g->next_group) { - ESP_LOGCONFIG(TAG, " Preset group: %s", g->name.c_str()); - for (auto p = g->first_preset; p != nullptr; p = p->next_preset) { - ESP_LOGCONFIG(TAG, " Preset: %s", p->name.c_str()); - } - } + public: + PresetGroup *first_group = nullptr; + PresetGroup *last_group = nullptr; + PresetGroup *active_group = nullptr; + + void dump_config() { + if (first_group != nullptr) { + ESP_LOGCONFIG(TAG, "Light Presets:"); + for (auto g = first_group; g != nullptr; g = g->next_group) { + ESP_LOGCONFIG(TAG, " Preset group: %s", g->name.c_str()); + for (auto p = g->first_preset; p != nullptr; p = p->next_preset) + ESP_LOGCONFIG(TAG, " Preset: %s", p->name.c_str()); + } } + } - void add_preset(Preset *preset) { - auto g = make_preset_group_(preset->group_name); - g->add_preset(preset); - } + void add_preset(Preset *preset) { + auto g = make_preset_group_(preset->group_name); + g->add_preset(preset); + } - PresetGroup *get_group(std::string g_name) { - for (auto g = first_group; g != nullptr; g = g->next_group) { - if (g->name == g_name) { - return g; - } - } - return nullptr; - } + PresetGroup *get_group(std::string g_name) { + for (auto g = first_group; g != nullptr; g = g->next_group) + if (g->name == g_name) + return g; + return nullptr; + } - void activate_next_group() { - if (active_group == nullptr) { - ESP_LOGW(TAG, "activate_next_group(): no preset groups defined"); - return; - } - active_group = active_group->next_group == nullptr - ? first_group : active_group->next_group; - if (active_group->active_preset == nullptr) { - ESP_LOGW(TAG, "activate_next_group(): no presets defined for group %s", - active_group->name.c_str()); - return; - } - active_group->active_preset->apply(); + void activate_next_group() { + if (active_group == nullptr) { + ESP_LOGW(TAG, "activate_next_group(): no preset groups defined"); + return; } - - void activate_next_preset() { - if (active_group == nullptr) { - ESP_LOGW(TAG, "activate_next_preset(): no preset groups defined"); - return; - } - auto p = active_group->active_preset; - if (p == nullptr) { - ESP_LOGW(TAG, "activate_next_preset(): no presets defined for group %s", - active_group->name.c_str()); - return; - } - active_group->active_preset = p->next_preset == nullptr - ? active_group->first_preset : p->next_preset; - active_group->active_preset->apply(); + active_group = active_group->next_group == nullptr ? first_group : active_group->next_group; + if (active_group->active_preset == nullptr) { + ESP_LOGW(TAG, "activate_next_group(): no presets defined for group %s", active_group->name.c_str()); + return; } + active_group->active_preset->apply(); + } - void activate_group(std::string g_name) { - auto g = get_group(g_name); - if (g == nullptr) { - ESP_LOGE(TAG, "activate_group(%s): preset group does not exist", - g_name.c_str()); - return; - } - auto p = g->active_preset; - if (p == nullptr) { - ESP_LOGW(TAG, "activate_group(%s): no presets defined for group", - g_name.c_str()); - return; - } - p->apply(); + void activate_next_preset() { + if (active_group == nullptr) { + ESP_LOGW(TAG, "activate_next_preset(): no preset groups defined"); + return; } - - void activate_preset(std::string g_name, std::string p_name) { - auto g = get_group(g_name); - if (g == nullptr) { - ESP_LOGE(TAG, "activate_preset(%s, %s): preset group '%s' does not exist", - g_name.c_str(), p_name.c_str(), g_name.c_str()); - return; - } - auto p = g->get_preset(p_name); - if (p == nullptr) { - ESP_LOGE(TAG, "activate_preset(%s, %s): preset '%s' does not exist in group '%s'", - g_name.c_str(), p_name.c_str(), p_name.c_str(), g->name.c_str()); - return; - } - p->apply(); + auto p = active_group->active_preset; + if (p == nullptr) { + ESP_LOGW(TAG, "activate_next_preset(): no presets defined for group %s", active_group->name.c_str()); + return; } - -protected: - PresetGroup *make_preset_group_(std::string g_name) { - auto g = get_group(g_name); - if (g == nullptr) { - g = new PresetGroup(g_name); - if (first_group == nullptr) { - first_group = last_group = active_group = g; - } else { - last_group->next_group = g; - last_group = g; - } - } - return g; + active_group->active_preset = p->next_preset == nullptr ? active_group->first_preset : p->next_preset; + active_group->active_preset->apply(); + } + + void activate_group(std::string g_name) { + auto g = get_group(g_name); + if (g == nullptr) { + ESP_LOGE(TAG, "activate_group(%s): preset group does not exist", g_name.c_str()); + return; + } + auto p = g->active_preset; + if (p == nullptr) { + ESP_LOGW(TAG, "activate_group(%s): no presets defined for group", g_name.c_str()); + return; + } + p->apply(); + } + + void activate_preset(std::string g_name, std::string p_name) { + auto g = get_group(g_name); + if (g == nullptr) { + ESP_LOGE(TAG, "activate_preset(%s, %s): preset group '%s' does not exist", g_name.c_str(), p_name.c_str(), + g_name.c_str()); + return; + } + auto p = g->get_preset(p_name); + if (p == nullptr) { + ESP_LOGE(TAG, "activate_preset(%s, %s): preset '%s' does not exist in group '%s'", g_name.c_str(), p_name.c_str(), + p_name.c_str(), g->name.c_str()); + return; + } + p->apply(); + } + + protected: + PresetGroup *make_preset_group_(std::string g_name) { + auto g = get_group(g_name); + if (g == nullptr) { + g = new PresetGroup(g_name); + if (first_group == nullptr) { + first_group = last_group = active_group = g; + } else { + last_group->next_group = g; + last_group = g; + } } + return g; + } }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/light_hal.h b/light_hal.h index 9a9f13a..9711b9b 100644 --- a/light_hal.h +++ b/light_hal.h @@ -1,48 +1,48 @@ -#pragma once +#pragma once -#include "esphome/core/component.h" -#include "esphome/components/ledc/ledc_output.h" #include "esphome/components/gpio/output/gpio_binary_output.h" +#include "esphome/components/ledc/ledc_output.h" +#include "esphome/core/component.h" namespace esphome { namespace xiaomi { namespace bslamp2 { class LightHAL : Component { -public: - void set_red_pin(ledc::LEDCOutput *pin) { red_ = pin; } - void set_green_pin(ledc::LEDCOutput *pin) { green_ = pin; } - void set_blue_pin(ledc::LEDCOutput *pin) { blue_ = pin; } - void set_white_pin(ledc::LEDCOutput *pin) { white_ = pin; } - void set_master1_pin(gpio::GPIOBinaryOutput *pin) { master1_ = pin; } - void set_master2_pin(gpio::GPIOBinaryOutput *pin) { master2_ = pin; } - - void turn_on() { - master1_->turn_on(); - master2_->turn_on(); - } - - void turn_off() { - master1_->turn_off(); - master2_->turn_off(); - } - - void set_rgbw(float r, float g, float b, float w) { - red_->set_level(r); - green_->set_level(g); - blue_->set_level(b); - white_->set_level(w); - } - -protected: - ledc::LEDCOutput *red_; - ledc::LEDCOutput *green_; - ledc::LEDCOutput *blue_; - ledc::LEDCOutput *white_; - gpio::GPIOBinaryOutput *master1_; - gpio::GPIOBinaryOutput *master2_; + public: + void set_red_pin(ledc::LEDCOutput *pin) { red_ = pin; } + void set_green_pin(ledc::LEDCOutput *pin) { green_ = pin; } + void set_blue_pin(ledc::LEDCOutput *pin) { blue_ = pin; } + void set_white_pin(ledc::LEDCOutput *pin) { white_ = pin; } + void set_master1_pin(gpio::GPIOBinaryOutput *pin) { master1_ = pin; } + void set_master2_pin(gpio::GPIOBinaryOutput *pin) { master2_ = pin; } + + void turn_on() { + master1_->turn_on(); + master2_->turn_on(); + } + + void turn_off() { + master1_->turn_off(); + master2_->turn_off(); + } + + void set_rgbw(float r, float g, float b, float w) { + red_->set_level(r); + green_->set_level(g); + blue_->set_level(b); + white_->set_level(w); + } + + protected: + ledc::LEDCOutput *red_; + ledc::LEDCOutput *green_; + ledc::LEDCOutput *blue_; + ledc::LEDCOutput *white_; + gpio::GPIOBinaryOutput *master1_; + gpio::GPIOBinaryOutput *master2_; }; -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/output/output.h b/output/output.h index 3ed852a..bfb92a8 100644 --- a/output/output.h +++ b/output/output.h @@ -1,9 +1,9 @@ -#pragma once +#pragma once -#include #include "../common.h" #include "../front_panel_hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace xiaomi { @@ -14,17 +14,15 @@ namespace bslamp2 { * level indicator on the Xiaomi Mijia Bedside Lamp 2 front panel. */ class XiaomiBslamp2FrontPanelLight : public output::FloatOutput, public Component { -public: - void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } + public: + void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } - void write_state(float level) { - front_panel_->set_light_level(level); - } + void write_state(float level) { front_panel_->set_light_level(level); } -protected: - FrontPanelHAL *front_panel_; + protected: + FrontPanelHAL *front_panel_; }; - -} // namespace bslamp2 -} // namespace yxiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/sensor/slider_sensor.h b/sensor/slider_sensor.h index b80c6ff..43ebce9 100644 --- a/sensor/slider_sensor.h +++ b/sensor/slider_sensor.h @@ -1,16 +1,16 @@ -#pragma once +#pragma once -#include #include "../common.h" #include "../front_panel_hal.h" #include "esphome/components/sensor/sensor.h" +#include namespace esphome { namespace xiaomi { namespace bslamp2 { /** - * A sensor for the touch slider on the front panel of the + * A sensor for the touch slider on the front panel of the * Xiaomi Mijia Bedside Lamp 2. * * This sensor publishes the level at which the slider was touched, so it @@ -20,46 +20,44 @@ namespace bslamp2 { * panel illumination (this is implemented by the slider output component). */ class XiaomiBslamp2SliderSensor : public sensor::Sensor, public Component { -public: - void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } - void set_range_from(float from) { range_from_ = from; } - void set_range_to(float to) { range_to_ = to; } - - void setup() { - slope_ = (range_to_ - range_from_) / 19.0f; - - front_panel_->add_on_event_callback( - [this](EVENT ev) { - if ((ev & FLAG_PART_MASK) == FLAG_PART_SLIDER) { - float level = (ev & FLAG_LEVEL_MASK) >> FLAG_LEVEL_SHIFT; - - // Slider level 1 is really hard to touch. It is between - // the power button and the slider space, so it doesn't - // look like this one was ever meant to be used, or that - // the design was faulty on this. Therefore, level 1 is - // ignored. The resulting range of levels is 0-19. - float corrected_level = max(0.0f, level - 2.0f); - - float final_level = range_from_ + (slope_ * corrected_level); - this->publish_state(final_level); - } - } - ); - } - - void dump_config() { - ESP_LOGCONFIG(TAG, "Front panel slider sensor:"); - ESP_LOGCONFIG(TAG, " Range from: %f", range_from_); - ESP_LOGCONFIG(TAG, " Range to: %f", range_to_); - } - -protected: - FrontPanelHAL *front_panel_; - float range_from_; - float range_to_; - float slope_; + public: + void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; } + void set_range_from(float from) { range_from_ = from; } + void set_range_to(float to) { range_to_ = to; } + + void setup() { + slope_ = (range_to_ - range_from_) / 19.0f; + + front_panel_->add_on_event_callback([this](EVENT ev) { + if ((ev & FLAG_PART_MASK) == FLAG_PART_SLIDER) { + float level = (ev & FLAG_LEVEL_MASK) >> FLAG_LEVEL_SHIFT; + + // Slider level 1 is really hard to touch. It is between + // the power button and the slider space, so it doesn't + // look like this one was ever meant to be used, or that + // the design was faulty on this. Therefore, level 1 is + // ignored. The resulting range of levels is 0-19. + float corrected_level = max(0.0f, level - 2.0f); + + float final_level = range_from_ + (slope_ * corrected_level); + this->publish_state(final_level); + } + }); + } + + void dump_config() { + ESP_LOGCONFIG(TAG, "Front panel slider sensor:"); + ESP_LOGCONFIG(TAG, " Range from: %f", range_from_); + ESP_LOGCONFIG(TAG, " Range to: %f", range_to_); + } + + protected: + FrontPanelHAL *front_panel_; + float range_from_; + float range_to_; + float slope_; }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/text_sensor/light_mode_text_sensor.h b/text_sensor/light_mode_text_sensor.h index a5b7571..45e9de5 100644 --- a/text_sensor/light_mode_text_sensor.h +++ b/text_sensor/light_mode_text_sensor.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "esphome/components/text_sensor/text_sensor.h" @@ -13,23 +13,23 @@ namespace bslamp2 { * The possible light modes are "off", "rgb", "white" and "night". */ class XiaomiBslamp2LightModeTextSensor : public text_sensor::TextSensor, public Component { -public: - void set_parent(XiaomiBslamp2LightOutput *light) { light_ = light; } + public: + void set_parent(XiaomiBslamp2LightOutput *light) { light_ = light; } - void setup() { - light_->add_on_light_mode_callback([this](std::string light_mode) { - if (last_light_mode_ != light_mode) { - publish_state(light_mode); - last_light_mode_ = light_mode; - } - }); - } + void setup() { + light_->add_on_light_mode_callback([this](std::string light_mode) { + if (last_light_mode_ != light_mode) { + publish_state(light_mode); + last_light_mode_ = light_mode; + } + }); + } -protected: - XiaomiBslamp2LightOutput *light_; - std::string last_light_mode_ = LIGHT_MODE_UNKNOWN; + protected: + XiaomiBslamp2LightOutput *light_; + std::string last_light_mode_ = LIGHT_MODE_UNKNOWN; }; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome