From b4ac0594922372b82088efc718a5a394c0b733a4 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 20 Apr 2021 01:13:32 +0200 Subject: [PATCH] Added multitouch to binary_sensor. --- binary_sensor/__init__.py | 74 +++++++++++++++++++++-------- binary_sensor/touch_binary_sensor.h | 38 ++++++++------- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/binary_sensor/__init__.py b/binary_sensor/__init__.py index 45ada32..782aa57 100644 --- a/binary_sensor/__init__.py +++ b/binary_sensor/__init__.py @@ -1,7 +1,8 @@ +import re import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_FOR from .. import ( bslamp2_ns, CODEOWNERS, CONF_FRONT_PANEL_HAL_ID, FrontPanelHAL @@ -11,29 +12,63 @@ AUTO_LOAD = ["xiaomi_bslamp2"] CONF_PART = "part" +# The identifier values match the bit values of the events as defined +# in ../front_panel_hal.h. PARTS = { - "ANY" : 0, - "POWER_BUTTON" : 1, - "POWER" : 1, - "COLOR_BUTTON" : 2, - "COLOR" : 2, - "SLIDER" : 3, + "POWER_BUTTON" : 0b001 << 1, + "POWER" : 0b001 << 1, + "COLOR_BUTTON" : 0b010 << 1, + "COLOR" : 0b010 << 1, + "SLIDER" : 0b100 << 1, } -def validate_part(value): - value = cv.string(value) - return cv.enum(PARTS, upper=True, space='_')(value) - XiaomiBslamp2TouchBinarySensor = bslamp2_ns.class_( "XiaomiBslamp2TouchBinarySensor", binary_sensor.BinarySensor, cg.Component) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(XiaomiBslamp2TouchBinarySensor), - cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), - cv.Optional(CONF_PART, default="ANY"): validate_part, - } -).extend(cv.COMPONENT_SCHEMA) +def get_part_id(value): + normalized = re.sub('\s+', '_', value.upper()) + try: + return PARTS[normalized] + except KeyError: + raise cv.Invalid(f"[{value}] is not a valid part identifier") + +def validate_for(value): + parts = set() + if isinstance(value, str): + parts.add(get_part_id(value)) + elif isinstance(value, list): + for x in value: + parts.add(get_part_id(x)) + else: + cv.Invalid("The value must be a single part identifier or a list of identifiers.") + return list(parts) + +def validate_part(value): + return [ get_part_id(value) ] + +def validate_binary_sensor(conf): + if CONF_PART in conf and CONF_FOR in conf: + raise cv.Invalid("Specify only one of [part] or [for]") + if CONF_PART in conf and not CONF_FOR in conf: + conf[CONF_FOR] = conf[CONF_PART] + if CONF_FOR not in conf: + raise cv.Invalid("'for' is a required option for [binary_sensor.xiaomi_bslamp2]") + return conf + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiBslamp2TouchBinarySensor), + cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), + # This option is not advertised in the documentation. It must be + # considered deprecated. I'm not announcing it as such yet. Not sure + # if it's useful to do so. + cv.Optional(CONF_PART): validate_part, + cv.Optional(CONF_FOR): validate_for, + } + ).extend(cv.COMPONENT_SCHEMA), + validate_binary_sensor, +) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) @@ -42,4 +77,5 @@ def to_code(config): front_panel_hal_var = yield cg.get_variable(config[CONF_FRONT_PANEL_HAL_ID]) cg.add(var.set_parent(front_panel_hal_var)) - cg.add(var.set_part(config[CONF_PART])) + for part_id in config[CONF_FOR]: + cg.add(var.include_part(part_id)) diff --git a/binary_sensor/touch_binary_sensor.h b/binary_sensor/touch_binary_sensor.h index 5771091..a4930fc 100644 --- a/binary_sensor/touch_binary_sensor.h +++ b/binary_sensor/touch_binary_sensor.h @@ -18,38 +18,44 @@ public: front_panel_ = front_panel; } - void set_part(int part) { - part_ = part; + void include_part(int part) { + match_ = match_ | part; } void setup() { front_panel_->add_on_event_callback( [this](EVENT ev) { - // Filter events by part, when requested. - if (part_ > 0) { - if ((ev & FLAG_PART_MASK) != (part_ << FLAG_PART_SHIFT)) { - return; - } + auto part = ev & FLAG_PART_MASK; + auto is_touch = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; + if (is_touch) { + active_ = active_ | part; + } else { + active_ = active_ & ~part; } - // Publish the new state, based on the touch/release status.. - auto on_or_off = (ev & FLAG_TYPE_MASK) == FLAG_TYPE_TOUCH; - this->publish_state(on_or_off); + + this->publish_state(active_ == match_); } ); } void dump_config() { ESP_LOGCONFIG(TAG, "Front panel binary_sensor:"); - ESP_LOGCONFIG(TAG, " Part: %s (id %d)", - (part_ == 1 ? "power button" : - part_ == 2 ? "color button" : - part_ == 3 ? "slider" : "any"), - part_); + ESP_LOGCONFIG(TAG, " Part(s):"); + if ((match_ & FLAG_PART_POWER) == FLAG_PART_POWER) { + ESP_LOGCONFIG(TAG, " - Power button"); + } + if ((match_ & FLAG_PART_COLOR) == FLAG_PART_COLOR) { + ESP_LOGCONFIG(TAG, " - Color button"); + } + if ((match_ & FLAG_PART_SLIDER) == FLAG_PART_SLIDER) { + ESP_LOGCONFIG(TAG, " - Slider"); + } } protected: FrontPanelHAL *front_panel_; - EVENT part_; + EVENT match_ = 0; + EVENT active_ = 0; }; } // namespace bslamp2