Binary sensor map implementation (#551)

* add binary_sensor_map c code

* add python file

* fixed python and C++ code for new framework

* renamed add_sensor to add_channel

* travis

* Updates

- Use struct for channels_ array - heap allocation is not really necessary here.
- any_active can also be written as mask != 0
- Update setup priority to DATA
- Use shorter TAG (name is already long; not important)
- Quotes around name
- Add icon to sensor
- Use new cv.typed_schema
- Change CONF_CHANNEL to CONF_BINARY_SENSOR - makes it clearer that this option accepts a binary sensor (and not for example an int)
- Add test


Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
Michiel van Turnhout 2019-05-28 20:41:10 +02:00 committed by Otto Winter
parent 5893506528
commit 9d26c16471
6 changed files with 174 additions and 0 deletions

View file

@ -0,0 +1,60 @@
#include "binary_sensor_map.h"
#include "esphome/core/log.h"
namespace esphome {
namespace binary_sensor_map {
static const char *TAG = "binary_sensor_map";
void BinarySensorMap::dump_config() { LOG_SENSOR(" ", "binary_sensor_map", this); }
void BinarySensorMap::loop() {
switch (this->sensor_type_) {
case BINARY_SENSOR_MAP_TYPE_GROUP:
this->process_group_();
break;
}
}
void BinarySensorMap::process_group_() {
float total_current_value = 0.0;
uint8_t num_active_sensors = 0;
uint64_t mask = 0x00;
// check all binary_sensors for its state. when active add its value to total_current_value.
// create a bitmask for the binary_sensor status on all channels
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
if (bs.binary_sensor->state) {
num_active_sensors++;
total_current_value += bs.sensor_value;
mask |= 1 << i;
}
}
// check if the sensor map was touched
if (mask != 0ULL) {
// did the bit_mask change or is it a new sensor touch
if (this->last_mask_ != mask) {
float publish_value = total_current_value / num_active_sensors;
ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
this->publish_state(publish_value);
}
} else if (this->last_mask_ != 0ULL) {
// is this a new sensor release
ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
this->publish_state(NAN);
}
this->last_mask_ = mask;
}
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
BinarySensorMapChannel sensor_channel{
.binary_sensor = sensor,
.sensor_value = value,
};
this->channels_.push_back(sensor_channel);
}
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
} // namespace binary_sensor_map
} // namespace esphome

View file

@ -0,0 +1,58 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace binary_sensor_map {
enum BinarySensorMapType {
BINARY_SENSOR_MAP_TYPE_GROUP,
};
struct BinarySensorMapChannel {
binary_sensor::BinarySensor *binary_sensor;
float sensor_value;
};
/** Class to group binary_sensors to one Sensor.
*
* Each binary sensor represents a float value in the group.
*/
class BinarySensorMap : public sensor::Sensor, public Component {
public:
void dump_config() override;
/**
* The loop checks all binary_sensor states
* When the binary_sensor reports a true value for its state, then the float value it represents is added to the
* total_current_value
*
* Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors
* average value. When the value changed and no sensors ar active we publish NAN.
* */
void loop() override;
float get_setup_priority() const override { return setup_priority::DATA; }
/** Add binary_sensors to the group.
* Each binary_sensor represents a float value when its state is true
*
* @param *sensor The binary sensor.
* @param value The value this binary_sensor represents
*/
void add_channel(binary_sensor::BinarySensor *sensor, float value);
void set_sensor_type(BinarySensorMapType sensor_type);
protected:
std::vector<BinarySensorMapChannel> channels_{};
BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
// this gives max 64 channels per binary_sensor_map
uint64_t last_mask_{0x00};
/**
* methods to process the types of binary_sensor_maps
* GROUP: process_group_() just map to a value
* */
void process_group_();
};
} // namespace binary_sensor_map
} // namespace esphome

View file

@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, binary_sensor
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR
DEPENDENCIES = ['binary_sensor']
binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
CONF_GROUP = 'group'
SENSOR_MAP_TYPES = {
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
}
entry = {
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_VALUE): cv.float_,
}
CONFIG_SCHEMA = cv.typed_schema({
CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({
cv.GenerateID(): cv.declare_id(BinarySensorMap),
cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)),
}),
}, lower=True)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
cg.add(var.set_sensor_type(constant))
for ch in config[CONF_CHANNELS]:
input_var = yield cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))

View file

@ -458,6 +458,7 @@ ICON_BATTERY = 'mdi:battery'
ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download' ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download'
ICON_BRIGHTNESS_5 = 'mdi:brightness-5' ICON_BRIGHTNESS_5 = 'mdi:brightness-5'
ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon' ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon'
ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline'
ICON_EMPTY = '' ICON_EMPTY = ''
ICON_FLASH = 'mdi:flash' ICON_FLASH = 'mdi:flash'
ICON_FLOWER = 'mdi:flower' ICON_FLOWER = 'mdi:flower'

View file

@ -152,6 +152,16 @@ sensor:
sensors: sensors:
- id: custom_sensor - id: custom_sensor
name: Custom Sensor name: Custom Sensor
- platform: binary_sensor_map
name: Binary Sensor Map
type: group
channels:
- binary_sensor: bin1
value: 10.0
- binary_sensor: bin2
value: 15.0
- binary_sensor: bin3
value: 100.0
time: time:
- platform: homeassistant - platform: homeassistant
@ -196,12 +206,15 @@ binary_sensor:
- platform: mpr121 - platform: mpr121
channel: 1 channel: 1
name: "touchkey1" name: "touchkey1"
id: bin1
- platform: mpr121 - platform: mpr121
channel: 2 channel: 2
name: "touchkey2" name: "touchkey2"
id: bin2
- platform: mpr121 - platform: mpr121
channel: 3 channel: 3
name: "touchkey3" name: "touchkey3"
id: bin3
on_press: on_press:
then: then:
- switch.toggle: mpr121_toggle - switch.toggle: mpr121_toggle