Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-04-13 08:12:48 +02:00 committed by GitHub
commit 6431bd552e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 264 additions and 51 deletions

View File

@ -23,6 +23,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
include:
- id: ci-custom

View File

@ -16,6 +16,9 @@ void BinarySensorMap::loop() {
case BINARY_SENSOR_MAP_TYPE_SUM:
this->process_sum_();
break;
case BINARY_SENSOR_MAP_TYPE_BAYESIAN:
this->process_bayesian_();
break;
}
}
@ -23,46 +26,51 @@ 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
// - check all binary_sensors for its state
// - if active, add its value to total_current_value.
// - creates a bitmask for the binary_sensor states 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;
total_current_value += bs.parameters.sensor_value;
mask |= 1ULL << i;
}
}
// check if the sensor map was touched
// potentially update state only if a binary_sensor is active
if (mask != 0ULL) {
// did the bit_mask change or is it a new sensor touch
// publish the average if the bitmask has changed
if (this->last_mask_ != mask) {
float publish_value = total_current_value / num_active_sensors;
this->publish_state(publish_value);
}
} else if (this->last_mask_ != 0ULL) {
// is this a new sensor release
// no buttons are pressed and the states have changed since last run, so publish NAN
ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
this->publish_state(NAN);
}
this->last_mask_ = mask;
}
void BinarySensorMap::process_sum_() {
float total_current_value = 0.0;
uint64_t mask = 0x00;
// - check all binary_sensor states
// - if active, add its value to total_current_value
// - creates a bitmask for the binary_sensor status on all channels
// - creates a bitmask for the binary_sensor states on all channels
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
if (bs.binary_sensor->state) {
total_current_value += bs.sensor_value;
total_current_value += bs.parameters.sensor_value;
mask |= 1ULL << i;
}
}
// update state only if the binary sensor states have changed or if no state has ever been sent on boot
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
if ((this->last_mask_ != mask) || (!this->has_state())) {
this->publish_state(total_current_value);
}
@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() {
this->last_mask_ = mask;
}
void BinarySensorMap::process_bayesian_() {
float posterior_probability = this->bayesian_prior_;
uint64_t mask = 0x00;
// - compute the posterior probability by taking the product of the predicate probablities for each observation
// - create a bitmask for the binary_sensor states on all channels/observations
for (size_t i = 0; i < this->channels_.size(); i++) {
auto bs = this->channels_[i];
posterior_probability *=
this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability,
bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false);
mask |= ((uint64_t) (bs.binary_sensor->state)) << i;
}
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
if ((this->last_mask_ != mask) || (!this->has_state())) {
this->publish_state(posterior_probability);
}
this->last_mask_ = mask;
}
float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true,
float prob_given_false) {
float prob_state_source_true = prob_given_true;
float prob_state_source_false = prob_given_false;
// if sensor is off, then we use the probabilities for the observation's complement
if (!sensor_state) {
prob_state_source_true = 1 - prob_given_true;
prob_state_source_false = 1 - prob_given_false;
}
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
}
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
BinarySensorMapChannel sensor_channel{
.binary_sensor = sensor,
.sensor_value = value,
.parameters{
.sensor_value = value,
},
};
this->channels_.push_back(sensor_channel);
}
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) {
BinarySensorMapChannel sensor_channel{
.binary_sensor = sensor,
.parameters{
.probabilities{
.given_true = prob_given_true,
.given_false = prob_given_false,
},
},
};
this->channels_.push_back(sensor_channel);
}
} // namespace binary_sensor_map
} // namespace esphome

View File

@ -12,51 +12,88 @@ namespace binary_sensor_map {
enum BinarySensorMapType {
BINARY_SENSOR_MAP_TYPE_GROUP,
BINARY_SENSOR_MAP_TYPE_SUM,
BINARY_SENSOR_MAP_TYPE_BAYESIAN,
};
struct BinarySensorMapChannel {
binary_sensor::BinarySensor *binary_sensor;
float sensor_value;
union {
float sensor_value;
struct {
float given_true;
float given_false;
} probabilities;
} parameters;
};
/** Class to group binary_sensors to one Sensor.
/** Class to map one or more binary_sensors to one Sensor.
*
* Each binary sensor represents a float value in the group.
* Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result
*/
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
* The loop calls the configured type processing method
*
* 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.
* */
* The processing method loops through all sensors and calculates the numerical result
* The result is only published if a binary sensor state has changed or, for some types, on initial boot
*/
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
/**
* Add binary_sensors to the group when only one parameter is needed for the configured mapping type.
*
* @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);
/**
* Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type.
*
* @param *sensor The binary sensor.
* @param prob_given_true Probability this observation is on when the Bayesian event is true
* @param prob_given_false Probability this observation is on when the Bayesian event is false
*/
void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false);
void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; };
protected:
std::vector<BinarySensorMapChannel> channels_{};
BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
// this gives max 64 channels per binary_sensor_map
// this allows a max of 64 channels/observations in order to keep track of binary_sensor states
uint64_t last_mask_{0x00};
// Bayesian event prior probability before taking into account any observations
float bayesian_prior_{};
/**
* methods to process the types of binary_sensor_maps
* GROUP: process_group_() just map to a value
* Methods to process the binary_sensor_maps types
*
* GROUP: process_group_() averages all the values
* ADD: process_add_() adds all the values
* BAYESIAN: process_bayesian_() computes the predicate probability
* */
void process_group_();
void process_sum_();
void process_bayesian_();
/**
* Computes the Bayesian predicate for a specific observation
* If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement
*
* @param sensor_state State of observation
* @param prior Prior probability before accounting for this observation
* @param prob_given_true Probability this observation is on when the Bayesian event is true
* @param prob_given_false Probability this observation is on when the Bayesian event is false
* */
float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false);
};
} // namespace binary_sensor_map

View File

@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_(
)
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
CONF_BAYESIAN = "bayesian"
CONF_PRIOR = "prior"
CONF_PROB_GIVEN_TRUE = "prob_given_true"
CONF_PROB_GIVEN_FALSE = "prob_given_false"
CONF_OBSERVATIONS = "observations"
SENSOR_MAP_TYPES = {
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN,
}
entry = {
entry_one_parameter = {
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_VALUE): cv.float_,
}
entry_bayesian_parameters = {
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1),
cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1),
}
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_GROUP: sensor.sensor_schema(
@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema(
).extend(
{
cv.Required(CONF_CHANNELS): cv.All(
cv.ensure_list(entry), cv.Length(min=1, max=64)
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
),
}
),
@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema(
).extend(
{
cv.Required(CONF_CHANNELS): cv.All(
cv.ensure_list(entry), cv.Length(min=1, max=64)
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
),
}
),
CONF_BAYESIAN: sensor.sensor_schema(
BinarySensorMap,
accuracy_decimals=2,
).extend(
{
cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1),
cv.Required(CONF_OBSERVATIONS): cv.All(
cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64)
),
}
),
@ -66,6 +90,17 @@ async def to_code(config):
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
cg.add(var.set_sensor_type(constant))
for ch in config[CONF_CHANNELS]:
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
if config[CONF_TYPE] == CONF_BAYESIAN:
cg.add(var.set_bayesian_prior(config[CONF_PRIOR]))
for obs in config[CONF_OBSERVATIONS]:
input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR])
cg.add(
var.add_channel(
input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE]
)
)
else:
for ch in config[CONF_CHANNELS]:
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))

View File

@ -34,6 +34,7 @@ ETHERNET_TYPES = {
"DP83848": EthernetType.ETHERNET_TYPE_DP83848,
"IP101": EthernetType.ETHERNET_TYPE_IP101,
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
}
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")

View File

@ -74,6 +74,10 @@ void EthernetComponent::setup() {
phy = esp_eth_phy_new_jl1101(&phy_config);
break;
}
case ETHERNET_TYPE_KSZ8081: {
phy = esp_eth_phy_new_ksz8081(&phy_config);
break;
}
default: {
this->mark_failed();
return;
@ -140,7 +144,7 @@ void EthernetComponent::loop() {
}
void EthernetComponent::dump_config() {
std::string eth_type;
const char *eth_type;
switch (this->type_) {
case ETHERNET_TYPE_LAN8720:
eth_type = "LAN8720";
@ -158,6 +162,14 @@ void EthernetComponent::dump_config() {
eth_type = "IP101";
break;
case ETHERNET_TYPE_JL1101:
eth_type = "JL1101";
break;
case ETHERNET_TYPE_KSZ8081:
eth_type = "KSZ8081";
break;
default:
eth_type = "Unknown";
break;
@ -170,7 +182,8 @@ void EthernetComponent::dump_config() {
}
ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_);
ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_);
ESP_LOGCONFIG(TAG, " Type: %s", eth_type.c_str());
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
ESP_LOGCONFIG(TAG, " PHY addr: %u", this->phy_addr_);
}
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }

View File

@ -14,11 +14,13 @@ namespace esphome {
namespace ethernet {
enum EthernetType {
ETHERNET_TYPE_LAN8720 = 0,
ETHERNET_TYPE_UNKNOWN = 0,
ETHERNET_TYPE_LAN8720,
ETHERNET_TYPE_RTL8201,
ETHERNET_TYPE_DP83848,
ETHERNET_TYPE_IP101,
ETHERNET_TYPE_JL1101,
ETHERNET_TYPE_KSZ8081,
};
struct ManualIP {
@ -69,7 +71,7 @@ class EthernetComponent : public Component {
int power_pin_{-1};
uint8_t mdc_pin_{23};
uint8_t mdio_pin_{18};
EthernetType type_{ETHERNET_TYPE_LAN8720};
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
optional<ManualIP> manual_ip_{};

View File

@ -63,7 +63,7 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum(
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),

View File

@ -122,11 +122,18 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
}
// Adjust limits to nice y_per_div boundaries
int yn = int(ymin / y_per_div);
int ym = int(ymax / y_per_div) + int(1 * (fmodf(ymax, y_per_div) != 0));
ymin = yn * y_per_div;
ymax = ym * y_per_div;
yrange = ymax - ymin;
int yn = 0;
int ym = 1;
if (!std::isnan(ymin) && !std::isnan(ymax)) {
yn = (int) floorf(ymin / y_per_div);
ym = (int) ceilf(ymax / y_per_div);
if (yn == ym) {
ym++;
}
ymin = yn * y_per_div;
ymax = ym * y_per_div;
yrange = ymax - ymin;
}
/// Draw grid
if (!std::isnan(this->gridspacing_y_)) {

View File

@ -60,7 +60,7 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
{
cv.GenerateID(): cv.declare_id(LightState),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent),
cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum(
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(

View File

@ -92,7 +92,7 @@ def switch_schema(
device_class: str = _UNDEF,
icon: str = _UNDEF,
block_inverted: bool = False,
default_restore_mode: str = "RESTORE_DEFAULT_OFF",
default_restore_mode: str = "ALWAYS_OFF",
):
schema = _SWITCH_SCHEMA.extend(
{

View File

@ -16,6 +16,7 @@ time_based_ns = cg.esphome_ns.namespace("time_based")
TimeBasedCover = time_based_ns.class_("TimeBasedCover", cover.Cover, cg.Component)
CONF_HAS_BUILT_IN_ENDSTOP = "has_built_in_endstop"
CONF_MANUAL_CONTROL = "manual_control"
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
{
@ -26,6 +27,7 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean,
cv.Optional(CONF_MANUAL_CONTROL, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE, default=True): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
@ -51,4 +53,5 @@ async def to_code(config):
)
cg.add(var.set_has_built_in_endstop(config[CONF_HAS_BUILT_IN_ENDSTOP]))
cg.add(var.set_manual_control(config[CONF_MANUAL_CONTROL]))
cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))

View File

@ -79,6 +79,14 @@ void TimeBasedCover::control(const CoverCall &call) {
auto pos = *call.get_position();
if (pos == this->position) {
// already at target
if (this->manual_control_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) {
// for covers with manual control switch, we can't rely on the computed position, so if
// the command triggered again, we'll assume it's in the opposite direction anyway.
auto op = pos == COVER_CLOSED ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;
this->position = pos == COVER_CLOSED ? COVER_OPEN : COVER_CLOSED;
this->target_position_ = pos;
this->start_direction_(op);
}
// for covers with built in end stop, we should send the command again
if (this->has_built_in_endstop_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) {
auto op = pos == COVER_CLOSED ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;

View File

@ -21,6 +21,7 @@ class TimeBasedCover : public cover::Cover, public Component {
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
cover::CoverTraits get_traits() override;
void set_has_built_in_endstop(bool value) { this->has_built_in_endstop_ = value; }
void set_manual_control(bool value) { this->manual_control_ = value; }
void set_assumed_state(bool value) { this->assumed_state_ = value; }
protected:
@ -44,6 +45,7 @@ class TimeBasedCover : public cover::Cover, public Component {
uint32_t last_publish_time_{0};
float target_position_{0};
bool has_built_in_endstop_{false};
bool manual_control_{false};
bool assumed_state_{false};
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
};

View File

@ -137,7 +137,7 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
const uint32_t pos = (x + y * this->get_width_internal()) / 8u;
const uint32_t pos = (x + y * this->get_width_controller()) / 8u;
const uint8_t subpos = x & 0x07;
// flip logic
if (!color.is_on()) {
@ -146,7 +146,9 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color
this->buffer_[pos] &= ~(0x80 >> subpos);
}
}
uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; }
uint32_t WaveshareEPaper::get_buffer_length_() {
return this->get_width_controller() * this->get_height_internal() / 8u;
}
void WaveshareEPaper::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
@ -291,7 +293,7 @@ void HOT WaveshareEPaperTypeA::display() {
// COMMAND SET RAM X ADDRESS START END POSITION
this->command(0x44);
this->data(0x00);
this->data((this->get_width_internal() - 1) >> 3);
this->data((this->get_width_controller() - 1) >> 3);
// COMMAND SET RAM Y ADDRESS START END POSITION
this->command(0x45);
this->data(this->get_height_internal() - 1);
@ -392,12 +394,26 @@ int WaveshareEPaperTypeA::get_width_internal() {
case TTGO_EPAPER_2_13_IN_B73:
case TTGO_EPAPER_2_13_IN_B74:
case TTGO_EPAPER_2_13_IN_B1:
return 122;
case WAVESHARE_EPAPER_2_9_IN:
case WAVESHARE_EPAPER_2_9_IN_V2:
return 128;
}
return 0;
}
// The controller of the 2.13" displays has a buffer larger than screen size
int WaveshareEPaperTypeA::get_width_controller() {
switch (this->model_) {
case WAVESHARE_EPAPER_2_13_IN:
case TTGO_EPAPER_2_13_IN:
case TTGO_EPAPER_2_13_IN_B73:
case TTGO_EPAPER_2_13_IN_B74:
case TTGO_EPAPER_2_13_IN_B1:
return 128;
default:
return this->get_width_internal();
}
}
int WaveshareEPaperTypeA::get_height_internal() {
switch (this->model_) {
case WAVESHARE_EPAPER_1_54_IN:

View File

@ -54,6 +54,8 @@ class WaveshareEPaper : public PollingComponent,
}
}
virtual int get_width_controller() { return this->get_width_internal(); };
uint32_t get_buffer_length_();
uint32_t reset_duration_{200};
@ -111,6 +113,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper {
int get_height_internal() override;
int get_width_controller() override;
uint32_t full_update_every_{30};
uint32_t at_update_{0};
WaveshareEPaperTypeAModel model_;

View File

@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2023.4.0-dev"
__version__ = "2023.5.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"

View File

@ -10,7 +10,7 @@ platformio==6.1.6 # When updating platformio, also update Dockerfile
esptool==4.5.1
click==8.1.3
esphome-dashboard==20230214.0
aioesphomeapi==13.5.1
aioesphomeapi==13.7.0
zeroconf==0.56.0
# esp-idf requires this, but doesn't bundle it by default

View File

@ -368,6 +368,32 @@ sensor:
- binary_sensor: bin3
value: 100.0
- platform: binary_sensor_map
name: Binary Sensor Map
type: sum
channels:
- binary_sensor: bin1
value: 10.0
- binary_sensor: bin2
value: 15.0
- binary_sensor: bin3
value: 100.0
- platform: binary_sensor_map
name: Binary Sensor Map
type: bayesian
prior: 0.4
observations:
- binary_sensor: bin1
prob_given_true: 0.9
prob_given_false: 0.4
- binary_sensor: bin2
prob_given_true: 0.7
prob_given_false: 0.05
- binary_sensor: bin3
prob_given_true: 0.8
prob_given_false: 0.2
- platform: bl0939
uart_id: uart_8
voltage: