Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-06-16 17:14:29 +02:00 committed by GitHub
commit 28c0094600
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 2421 additions and 190 deletions

View file

@ -40,6 +40,7 @@
"yaml.customTags": [ "yaml.customTags": [
"!secret scalar", "!secret scalar",
"!lambda scalar", "!lambda scalar",
"!extend scalar",
"!include_dir_named scalar", "!include_dir_named scalar",
"!include_dir_list scalar", "!include_dir_list scalar",
"!include_dir_merge_list scalar", "!include_dir_merge_list scalar",

18
.vscode/tasks.json vendored
View file

@ -36,6 +36,24 @@
] ]
} }
] ]
},
{
"label": "Generate proto files",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": [
"./script/api_protobuf/api_protobuf.py"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "never",
"close": true,
"panel": "new"
},
"problemMatcher": []
} }
] ]
} }

View file

@ -19,6 +19,7 @@ esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix esphome/components/am43/sensor/* @buxtronix
@ -278,6 +279,7 @@ esphome/components/tca9548a/* @andreashergert1984
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11
esphome/components/thermostat/* @kbx81 esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter esphome/components/time/* @OttoWinter
esphome/components/tlc5947/* @rnauber esphome/components/tlc5947/* @rnauber

View file

@ -0,0 +1,165 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority
from esphome.const import (
CONF_ID,
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_CODE,
)
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11"]
IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered"
CONF_ON_CLEARED = "on_cleared"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
StateTrigger = alarm_control_panel_ns.class_(
"StateTrigger", automation.Trigger.template()
)
TriggeredTrigger = alarm_control_panel_ns.class_(
"TriggeredTrigger", automation.Trigger.template()
)
ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template()
)
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
}
),
}
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
cv.Optional(CONF_CODE): cv.templatable(cv.string),
}
)
ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
}
)
async def setup_alarm_control_panel_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_alarm_control_panel(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_alarm_control_panel(var))
await setup_alarm_control_panel_core_(var, config)
@automation.register_action(
"alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.disarm", DisarmAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.pending", PendingAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_pending_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
@automation.register_action(
"alarm_control_panel.triggered", TriggeredAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_trigger_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
return var
@automation.register_condition(
"alarm_control_panel.is_armed",
AlarmControlPanelCondition,
ALARM_CONTROL_PANEL_CONDITION_SCHEMA,
)
async def alarm_control_panel_is_armed_to_code(
config, condition_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)
cg.add_define("USE_ALARM_CONTROL_PANEL")

View file

@ -0,0 +1,111 @@
#include <utility>
#include "alarm_control_panel.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); }
bool AlarmControlPanel::is_state_armed(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_ARMED_AWAY:
case ACP_STATE_ARMED_HOME:
case ACP_STATE_ARMED_NIGHT:
case ACP_STATE_ARMED_VACATION:
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return true;
default:
return false;
}
};
void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->last_update_ = millis();
if (state != this->current_state_) {
auto prev_state = this->current_state_;
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
this->state_callback_.call();
if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call();
}
if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call();
}
if (state == this->desired_state_) {
// only store when in the desired state
this->pref_.save(&state);
}
}
}
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
this->triggered_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback));
}
void AlarmControlPanel::arm_away(optional<std::string> code) {
auto call = this->make_call();
call.arm_away();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_home(optional<std::string> code) {
auto call = this->make_call();
call.arm_home();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_night(optional<std::string> code) {
auto call = this->make_call();
call.arm_night();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_vacation(optional<std::string> code) {
auto call = this->make_call();
call.arm_vacation();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::arm_custom_bypass(optional<std::string> code) {
auto call = this->make_call();
call.arm_custom_bypass();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
void AlarmControlPanel::disarm(optional<std::string> code) {
auto call = this->make_call();
call.disarm();
if (code.has_value())
call.set_code(code.value());
call.perform();
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,136 @@
#pragma once
#include <map>
#include "alarm_control_panel_call.h"
#include "alarm_control_panel_state.h"
#include "esphome/core/automation.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
enum AlarmControlPanelFeature : uint8_t {
// Matches Home Assistant values
ACP_FEAT_ARM_HOME = 1 << 0,
ACP_FEAT_ARM_AWAY = 1 << 1,
ACP_FEAT_ARM_NIGHT = 1 << 2,
ACP_FEAT_TRIGGER = 1 << 3,
ACP_FEAT_ARM_CUSTOM_BYPASS = 1 << 4,
ACP_FEAT_ARM_VACATION = 1 << 5,
};
class AlarmControlPanel : public EntityBase {
public:
/** Make a AlarmControlPanelCall
*
*/
AlarmControlPanelCall make_call();
/** Set the state of the alarm_control_panel.
*
* @param state The AlarmControlPanelState.
*/
void publish_state(AlarmControlPanelState state);
/** Add a callback for when the state of the alarm_control_panel changes
*
* @param callback The callback function
*/
void add_on_state_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to triggered
*
* @param callback The callback function
*/
void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered
*
* @param callback The callback function
*/
void add_on_cleared_callback(std::function<void()> &&callback);
/** A numeric representation of the supported features as per HomeAssistant
*
*/
virtual uint32_t get_supported_features() const = 0;
/** Returns if the alarm_control_panel has a code
*
*/
virtual bool get_requires_code() const = 0;
/** Returns if the alarm_control_panel requires a code to arm
*
*/
virtual bool get_requires_code_to_arm() const = 0;
/** arm the alarm in away mode
*
* @param code The code
*/
void arm_away(optional<std::string> code = nullopt);
/** arm the alarm in home mode
*
* @param code The code
*/
void arm_home(optional<std::string> code = nullopt);
/** arm the alarm in night mode
*
* @param code The code
*/
void arm_night(optional<std::string> code = nullopt);
/** arm the alarm in vacation mode
*
* @param code The code
*/
void arm_vacation(optional<std::string> code = nullopt);
/** arm the alarm in custom bypass mode
*
* @param code The code
*/
void arm_custom_bypass(optional<std::string> code = nullopt);
/** disarm the alarm
*
* @param code The code
*/
void disarm(optional<std::string> code = nullopt);
/** Get the state
*
*/
AlarmControlPanelState get_state() const { return this->current_state_; }
// is the state one of the armed states
bool is_state_armed(AlarmControlPanelState state);
protected:
friend AlarmControlPanelCall;
// in order to store last panel state in flash
ESPPreferenceObject pref_;
// current state
AlarmControlPanelState current_state_;
// the desired (or previous) state
AlarmControlPanelState desired_state_;
// last time the state was updated
uint32_t last_update_;
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback
CallbackManager<void()> state_callback_{};
// trigger callback
CallbackManager<void()> triggered_callback_{};
// clear callback
CallbackManager<void()> cleared_callback_{};
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,99 @@
#include "alarm_control_panel_call.h"
#include "alarm_control_panel.h"
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
static const char *const TAG = "alarm_control_panel";
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
AlarmControlPanelCall &AlarmControlPanelCall::set_code(const std::string &code) {
this->code_ = code;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_away() {
this->state_ = ACP_STATE_ARMED_AWAY;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_home() {
this->state_ = ACP_STATE_ARMED_HOME;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_night() {
this->state_ = ACP_STATE_ARMED_NIGHT;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_vacation() {
this->state_ = ACP_STATE_ARMED_VACATION;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::arm_custom_bypass() {
this->state_ = ACP_STATE_ARMED_CUSTOM_BYPASS;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::disarm() {
this->state_ = ACP_STATE_DISARMED;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::pending() {
this->state_ = ACP_STATE_PENDING;
return *this;
}
AlarmControlPanelCall &AlarmControlPanelCall::triggered() {
this->state_ = ACP_STATE_TRIGGERED;
return *this;
}
const optional<AlarmControlPanelState> &AlarmControlPanelCall::get_state() const { return this->state_; }
const optional<std::string> &AlarmControlPanelCall::get_code() const { return this->code_; }
void AlarmControlPanelCall::validate_() {
if (this->state_.has_value()) {
auto state = *this->state_;
if (this->parent_->is_state_armed(state) && this->parent_->get_state() != ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot arm when not disarmed");
this->state_.reset();
return;
}
if (state == ACP_STATE_PENDING && this->parent_->get_state() == ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot trip alarm when disarmed");
this->state_.reset();
return;
}
if (state == ACP_STATE_DISARMED &&
!(this->parent_->is_state_armed(this->parent_->get_state()) ||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
ESP_LOGW(TAG, "Cannot disarm when not armed");
this->state_.reset();
return;
}
if (state == ACP_STATE_ARMED_HOME && (this->parent_->get_supported_features() & ACP_FEAT_ARM_HOME) == 0) {
ESP_LOGW(TAG, "Cannot arm home when not supported");
this->state_.reset();
return;
}
}
}
void AlarmControlPanelCall::perform() {
this->validate_();
if (this->state_) {
this->parent_->control(*this);
}
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include "alarm_control_panel_state.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace alarm_control_panel {
class AlarmControlPanel;
class AlarmControlPanelCall {
public:
AlarmControlPanelCall(AlarmControlPanel *parent);
AlarmControlPanelCall &set_code(const std::string &code);
AlarmControlPanelCall &arm_away();
AlarmControlPanelCall &arm_home();
AlarmControlPanelCall &arm_night();
AlarmControlPanelCall &arm_vacation();
AlarmControlPanelCall &arm_custom_bypass();
AlarmControlPanelCall &disarm();
AlarmControlPanelCall &pending();
AlarmControlPanelCall &triggered();
void perform();
const optional<AlarmControlPanelState> &get_state() const;
const optional<std::string> &get_code() const;
protected:
AlarmControlPanel *parent_;
optional<std::string> code_{};
optional<AlarmControlPanelState> state_{};
void validate_();
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,34 @@
#include "alarm_control_panel_state.h"
namespace esphome {
namespace alarm_control_panel {
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
switch (state) {
case ACP_STATE_DISARMED:
return LOG_STR("DISARMED");
case ACP_STATE_ARMED_HOME:
return LOG_STR("ARMED_HOME");
case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT");
case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return LOG_STR("ARMED_CUSTOM_BYPASS");
case ACP_STATE_PENDING:
return LOG_STR("PENDING");
case ACP_STATE_ARMING:
return LOG_STR("ARMING");
case ACP_STATE_DISARMING:
return LOG_STR("DISARMING");
case ACP_STATE_TRIGGERED:
return LOG_STR("TRIGGERED");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include "esphome/core/log.h"
namespace esphome {
namespace alarm_control_panel {
enum AlarmControlPanelState : uint8_t {
ACP_STATE_DISARMED = 0,
ACP_STATE_ARMED_HOME = 1,
ACP_STATE_ARMED_AWAY = 2,
ACP_STATE_ARMED_NIGHT = 3,
ACP_STATE_ARMED_VACATION = 4,
ACP_STATE_ARMED_CUSTOM_BYPASS = 5,
ACP_STATE_PENDING = 6,
ACP_STATE_ARMING = 7,
ACP_STATE_DISARMING = 8,
ACP_STATE_TRIGGERED = 9
};
/** Returns a string representation of the state.
*
* @param state The AlarmControlPanelState.
*/
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -0,0 +1,115 @@
#pragma once
#include "esphome/core/automation.h"
#include "alarm_control_panel.h"
namespace esphome {
namespace alarm_control_panel {
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
}
};
class TriggeredTrigger : public Trigger<> {
public:
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
}
};
class ClearedTrigger : public Trigger<> {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
}
};
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
public:
explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_away();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
public:
explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_home();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction : public Action<Ts...> {
public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class PendingAction : public Action<Ts...> {
public:
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class TriggeredAction : public Action<Ts...> {
public:
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
public:
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
bool check(Ts... x) override {
return this->parent_->is_state_armed(this->parent_->get_state()) ||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
}
protected:
AlarmControlPanel *parent_;
};
} // namespace alarm_control_panel
} // namespace esphome

View file

@ -56,6 +56,8 @@ service APIConnection {
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
} }
@ -1454,3 +1456,63 @@ message VoiceAssistantEventResponse {
VoiceAssistantEvent event_type = 1; VoiceAssistantEvent event_type = 1;
repeated VoiceAssistantEventData data = 2; repeated VoiceAssistantEventData data = 2;
} }
// ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState {
ALARM_STATE_DISARMED = 0;
ALARM_STATE_ARMED_HOME = 1;
ALARM_STATE_ARMED_AWAY = 2;
ALARM_STATE_ARMED_NIGHT = 3;
ALARM_STATE_ARMED_VACATION = 4;
ALARM_STATE_ARMED_CUSTOM_BYPASS = 5;
ALARM_STATE_PENDING = 6;
ALARM_STATE_ARMING = 7;
ALARM_STATE_DISARMING = 8;
ALARM_STATE_TRIGGERED = 9;
}
enum AlarmControlPanelStateCommand {
ALARM_CONTROL_PANEL_DISARM = 0;
ALARM_CONTROL_PANEL_ARM_AWAY = 1;
ALARM_CONTROL_PANEL_ARM_HOME = 2;
ALARM_CONTROL_PANEL_ARM_NIGHT = 3;
ALARM_CONTROL_PANEL_ARM_VACATION = 4;
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5;
ALARM_CONTROL_PANEL_TRIGGER = 6;
}
message ListEntitiesAlarmControlPanelResponse {
option (id) = 94;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 supported_features = 8;
bool requires_code = 9;
bool requires_code_to_arm = 10;
}
message AlarmControlPanelStateResponse {
option (id) = 95;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
fixed32 key = 1;
AlarmControlPanelState state = 2;
}
message AlarmControlPanelCommandRequest {
option (id) = 96;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
fixed32 key = 1;
AlarmControlPanelStateCommand command = 2;
string code = 3;
}

View file

@ -931,6 +931,64 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
if (!this->state_subscription_)
return false;
AlarmControlPanelStateResponse resp{};
resp.key = a_alarm_control_panel->get_object_id_hash();
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
return this->send_alarm_control_panel_state_response(resp);
}
bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
ListEntitiesAlarmControlPanelResponse msg;
msg.key = a_alarm_control_panel->get_object_id_hash();
msg.object_id = a_alarm_control_panel->get_object_id();
msg.name = a_alarm_control_panel->get_name();
msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
msg.icon = a_alarm_control_panel->get_icon();
msg.disabled_by_default = a_alarm_control_panel->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_alarm_control_panel->get_entity_category());
msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
return this->send_list_entities_alarm_control_panel_response(msg);
}
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
if (a_alarm_control_panel == nullptr)
return;
auto call = a_alarm_control_panel->make_call();
switch (msg.command) {
case enums::ALARM_CONTROL_PANEL_DISARM:
call.disarm();
break;
case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
call.arm_away();
break;
case enums::ALARM_CONTROL_PANEL_ARM_HOME:
call.arm_home();
break;
case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
call.arm_night();
break;
case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
call.arm_vacation();
break;
case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
call.arm_custom_bypass();
break;
case enums::ALARM_CONTROL_PANEL_TRIGGER:
call.pending();
break;
}
call.set_code(msg.code);
call.perform();
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;

View file

@ -129,6 +129,12 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override; void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override { void on_ping_response(const PingResponse &value) override {
// we initiated ping // we initiated ping

View file

@ -433,6 +433,57 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
} }
} }
#endif #endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
switch (value) {
case enums::ALARM_STATE_DISARMED:
return "ALARM_STATE_DISARMED";
case enums::ALARM_STATE_ARMED_HOME:
return "ALARM_STATE_ARMED_HOME";
case enums::ALARM_STATE_ARMED_AWAY:
return "ALARM_STATE_ARMED_AWAY";
case enums::ALARM_STATE_ARMED_NIGHT:
return "ALARM_STATE_ARMED_NIGHT";
case enums::ALARM_STATE_ARMED_VACATION:
return "ALARM_STATE_ARMED_VACATION";
case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS:
return "ALARM_STATE_ARMED_CUSTOM_BYPASS";
case enums::ALARM_STATE_PENDING:
return "ALARM_STATE_PENDING";
case enums::ALARM_STATE_ARMING:
return "ALARM_STATE_ARMING";
case enums::ALARM_STATE_DISARMING:
return "ALARM_STATE_DISARMING";
case enums::ALARM_STATE_TRIGGERED:
return "ALARM_STATE_TRIGGERED";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::AlarmControlPanelStateCommand value) {
switch (value) {
case enums::ALARM_CONTROL_PANEL_DISARM:
return "ALARM_CONTROL_PANEL_DISARM";
case enums::ALARM_CONTROL_PANEL_ARM_AWAY:
return "ALARM_CONTROL_PANEL_ARM_AWAY";
case enums::ALARM_CONTROL_PANEL_ARM_HOME:
return "ALARM_CONTROL_PANEL_ARM_HOME";
case enums::ALARM_CONTROL_PANEL_ARM_NIGHT:
return "ALARM_CONTROL_PANEL_ARM_NIGHT";
case enums::ALARM_CONTROL_PANEL_ARM_VACATION:
return "ALARM_CONTROL_PANEL_ARM_VACATION";
case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS:
return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS";
case enums::ALARM_CONTROL_PANEL_TRIGGER:
return "ALARM_CONTROL_PANEL_TRIGGER";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
@ -6436,6 +6487,217 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->supported_features = value.as_uint32();
return true;
}
case 9: {
this->requires_code = value.as_bool();
return true;
}
case 10: {
this->requires_code_to_arm = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesAlarmControlPanelResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->supported_features);
buffer.encode_bool(9, this->requires_code);
buffer.encode_bool(10, this->requires_code_to_arm);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesAlarmControlPanelResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" supported_features: ");
sprintf(buffer, "%u", this->supported_features);
out.append(buffer);
out.append("\n");
out.append(" requires_code: ");
out.append(YESNO(this->requires_code));
out.append("\n");
out.append(" requires_code_to_arm: ");
out.append(YESNO(this->requires_code_to_arm));
out.append("\n");
out.append("}");
}
#endif
bool AlarmControlPanelStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_enum<enums::AlarmControlPanelState>();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::AlarmControlPanelState>(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::AlarmControlPanelState>(this->state));
out.append("\n");
out.append("}");
}
#endif
bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->command = value.as_enum<enums::AlarmControlPanelStateCommand>();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
this->code = value.as_string();
return true;
}
default:
return false;
}
}
bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void AlarmControlPanelCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::AlarmControlPanelStateCommand>(2, this->command);
buffer.encode_string(3, this->code);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" command: ");
out.append(proto_enum_to_string<enums::AlarmControlPanelStateCommand>(this->command));
out.append("\n");
out.append(" code: ");
out.append("'").append(this->code).append("'");
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -176,6 +176,27 @@ enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_TTS_START = 7, VOICE_ASSISTANT_TTS_START = 7,
VOICE_ASSISTANT_TTS_END = 8, VOICE_ASSISTANT_TTS_END = 8,
}; };
enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0,
ALARM_STATE_ARMED_HOME = 1,
ALARM_STATE_ARMED_AWAY = 2,
ALARM_STATE_ARMED_NIGHT = 3,
ALARM_STATE_ARMED_VACATION = 4,
ALARM_STATE_ARMED_CUSTOM_BYPASS = 5,
ALARM_STATE_PENDING = 6,
ALARM_STATE_ARMING = 7,
ALARM_STATE_DISARMING = 8,
ALARM_STATE_TRIGGERED = 9,
};
enum AlarmControlPanelStateCommand : uint32_t {
ALARM_CONTROL_PANEL_DISARM = 0,
ALARM_CONTROL_PANEL_ARM_AWAY = 1,
ALARM_CONTROL_PANEL_ARM_HOME = 2,
ALARM_CONTROL_PANEL_ARM_NIGHT = 3,
ALARM_CONTROL_PANEL_ARM_VACATION = 4,
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
ALARM_CONTROL_PANEL_TRIGGER = 6,
};
} // namespace enums } // namespace enums
@ -1680,6 +1701,56 @@ class VoiceAssistantEventResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
uint32_t supported_features{0};
bool requires_code{false};
bool requires_code_to_arm{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class AlarmControlPanelStateResponse : public ProtoMessage {
public:
uint32_t key{0};
enums::AlarmControlPanelState state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class AlarmControlPanelCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
enums::AlarmControlPanelStateCommand command{};
std::string code{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -476,6 +476,25 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_alarm_control_panel_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesAlarmControlPanelResponse>(msg, 94);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_alarm_control_panel_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<AlarmControlPanelStateResponse>(msg, 95);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -883,6 +902,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
#endif #endif
this->on_voice_assistant_event_response(msg); this->on_voice_assistant_event_response(msg);
#endif
break;
}
case 96: {
#ifdef USE_ALARM_CONTROL_PANEL
AlarmControlPanelCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
#endif
this->on_alarm_control_panel_command_request(msg);
#endif #endif
break; break;
} }
@ -1295,6 +1325,19 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
this->subscribe_voice_assistant(msg); this->subscribe_voice_assistant(msg);
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->alarm_control_panel_command(msg);
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -239,6 +239,15 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -324,6 +333,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &msg) override;
@ -405,6 +417,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif
}; };
} // namespace api } // namespace api

View file

@ -338,5 +338,14 @@ void APIServer::stop_voice_assistant() {
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -85,6 +85,10 @@ class APIServer : public Component, public Controller {
void stop_voice_assistant(); void stop_voice_assistant();
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
bool is_connected() const; bool is_connected() const;
struct HomeAssistantStateSubscription { struct HomeAssistantStateSubscription {

View file

@ -69,6 +69,11 @@ bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_play
return this->client_->send_media_player_info(media_player); return this->client_->send_media_player_info(media_player);
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -54,6 +54,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override; bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif #endif
bool on_end() override; bool on_end() override;

View file

@ -55,6 +55,11 @@ bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_play
return this->client_->send_media_player_state(media_player); return this->client_->send_media_player_state(media_player);
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
}
#endif
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api } // namespace api

View file

@ -51,6 +51,9 @@ class InitialStateIterator : public ComponentIterator {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override; bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif #endif
protected: protected:
APIConnection *client_; APIConnection *client_;

View file

@ -15,6 +15,25 @@ static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 255); const Color COLOR_OFF(0, 0, 0, 255);
const Color COLOR_ON(255, 255, 255, 255); const Color COLOR_ON(255, 255, 255, 255);
static int image_type_to_bpp(ImageType type) {
switch (type) {
case IMAGE_TYPE_BINARY:
return 1;
case IMAGE_TYPE_GRAYSCALE:
return 8;
case IMAGE_TYPE_RGB565:
return 16;
case IMAGE_TYPE_RGB24:
return 24;
case IMAGE_TYPE_RGBA:
return 32;
default:
return 0;
}
}
static int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; }
void Rect::expand(int16_t horizontal, int16_t vertical) { void Rect::expand(int16_t horizontal, int16_t vertical) {
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
this->x = this->x - horizontal; this->x = this->x - horizontal;
@ -306,63 +325,8 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) { void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
bool transparent = image->has_transparency(); image->draw(x, y, this, color_on, color_off);
switch (image->get_type()) {
case IMAGE_TYPE_BINARY: {
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
if (image->get_pixel(img_x, img_y)) {
this->draw_pixel_at(x + img_x, y + img_y, color_on);
} else if (!transparent) {
this->draw_pixel_at(x + img_x, y + img_y, color_off);
}
}
}
break;
}
case IMAGE_TYPE_GRAYSCALE:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
auto color = image->get_grayscale_pixel(img_x, img_y);
if (color.w >= 0x80) {
this->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGB565:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
auto color = image->get_rgb565_pixel(img_x, img_y);
if (color.w >= 0x80) {
this->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGB24:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
auto color = image->get_color_pixel(img_x, img_y);
if (color.w >= 0x80) {
this->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGBA:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
auto color = image->get_rgba_pixel(img_x, img_y);
if (color.w >= 0x80) {
this->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
}
} }
#ifdef USE_GRAPH #ifdef USE_GRAPH
@ -637,23 +601,91 @@ Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : basel
glyphs_.emplace_back(&data[i]); glyphs_.emplace_back(&data[i]);
} }
bool Image::get_pixel(int x, int y) const { void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
switch (type_) {
case IMAGE_TYPE_BINARY: {
for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) {
if (this->get_binary_pixel_(img_x, img_y)) {
display->draw_pixel_at(x + img_x, y + img_y, color_on);
} else if (!this->transparent_) {
display->draw_pixel_at(x + img_x, y + img_y, color_off);
}
}
}
break;
}
case IMAGE_TYPE_GRAYSCALE:
for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) {
auto color = this->get_grayscale_pixel_(img_x, img_y);
if (color.w >= 0x80) {
display->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGB565:
for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) {
auto color = this->get_rgb565_pixel_(img_x, img_y);
if (color.w >= 0x80) {
display->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGB24:
for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) {
auto color = this->get_rgb24_pixel_(img_x, img_y);
if (color.w >= 0x80) {
display->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
case IMAGE_TYPE_RGBA:
for (int img_x = 0; img_x < width_; img_x++) {
for (int img_y = 0; img_y < height_; img_y++) {
auto color = this->get_rgba_pixel_(img_x, img_y);
if (color.w >= 0x80) {
display->draw_pixel_at(x + img_x, y + img_y, color);
}
}
}
break;
}
}
Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return false; return color_off;
switch (this->type_) {
case IMAGE_TYPE_BINARY:
return this->get_binary_pixel_(x, y) ? color_on : color_off;
case IMAGE_TYPE_GRAYSCALE:
return this->get_grayscale_pixel_(x, y);
case IMAGE_TYPE_RGB565:
return this->get_rgb565_pixel_(x, y);
case IMAGE_TYPE_RGB24:
return this->get_rgb24_pixel_(x, y);
case IMAGE_TYPE_RGBA:
return this->get_rgba_pixel_(x, y);
default:
return color_off;
}
}
bool Image::get_binary_pixel_(int x, int y) const {
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t pos = x + y * width_8; const uint32_t pos = x + y * width_8;
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
} }
Color Image::get_rgba_pixel(int x, int y) const { Color Image::get_rgba_pixel_(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t pos = (x + y * this->width_) * 4; const uint32_t pos = (x + y * this->width_) * 4;
return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3)); progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
} }
Color Image::get_color_pixel(int x, int y) const { Color Image::get_rgb24_pixel_(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t pos = (x + y * this->width_) * 3; const uint32_t pos = (x + y * this->width_) * 3;
Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
progmem_read_byte(this->data_start_ + pos + 2)); progmem_read_byte(this->data_start_ + pos + 2));
@ -666,9 +698,7 @@ Color Image::get_color_pixel(int x, int y) const {
} }
return color; return color;
} }
Color Image::get_rgb565_pixel(int x, int y) const { Color Image::get_rgb565_pixel_(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t pos = (x + y * this->width_) * 2; const uint32_t pos = (x + y * this->width_) * 2;
uint16_t rgb565 = uint16_t rgb565 =
progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
@ -684,9 +714,7 @@ Color Image::get_rgb565_pixel(int x, int y) const {
} }
return color; return color;
} }
Color Image::get_grayscale_pixel(int x, int y) const { Color Image::get_grayscale_pixel_(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t pos = (x + y * this->width_); const uint32_t pos = (x + y * this->width_);
const uint8_t gray = progmem_read_byte(this->data_start_ + pos); const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF; uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
@ -697,80 +725,10 @@ int Image::get_height() const { return this->height_; }
ImageType Image::get_type() const { return this->type_; } ImageType Image::get_type() const { return this->type_; }
Image::Image(const uint8_t *data_start, int width, int height, ImageType type) Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
: width_(width), height_(height), type_(type), data_start_(data_start) {} : width_(width), height_(height), type_(type), data_start_(data_start) {}
int Image::get_current_frame() const { return 0; }
bool Animation::get_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return false;
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
return false;
const uint32_t pos = x + y * width_8 + frame_index;
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
}
Color Animation::get_rgba_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index) * 4;
return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
}
Color Animation::get_color_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
progmem_read_byte(this->data_start_ + pos + 2));
if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) {
// (0, 0, 1) has been defined as transparent color for non-alpha images.
// putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if)
color.w = 0;
} else {
color.w = 0xFF;
}
return color;
}
Color Animation::get_rgb565_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
uint16_t rgb565 =
progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
auto r = (rgb565 & 0xF800) >> 11;
auto g = (rgb565 & 0x07E0) >> 5;
auto b = rgb565 & 0x001F;
Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
if (rgb565 == 0x0020 && transparent_) {
// darkest green has been defined as transparent color for transparent RGB565 images.
color.w = 0;
} else {
color.w = 0xFF;
}
return color;
}
Color Animation::get_grayscale_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index);
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
return Color(gray, gray, gray, alpha);
}
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
: Image(data_start, width, height, type), : Image(data_start, width, height, type),
animation_data_start_(data_start),
current_frame_(0), current_frame_(0),
animation_frame_count_(animation_frame_count), animation_frame_count_(animation_frame_count),
loop_start_frame_(0), loop_start_frame_(0),
@ -797,12 +755,16 @@ void Animation::next_frame() {
this->loop_current_iteration_ = 1; this->loop_current_iteration_ = 1;
this->current_frame_ = 0; this->current_frame_ = 0;
} }
this->update_data_start_();
} }
void Animation::prev_frame() { void Animation::prev_frame() {
this->current_frame_--; this->current_frame_--;
if (this->current_frame_ < 0) { if (this->current_frame_ < 0) {
this->current_frame_ = this->animation_frame_count_ - 1; this->current_frame_ = this->animation_frame_count_ - 1;
} }
this->update_data_start_();
} }
void Animation::set_frame(int frame) { void Animation::set_frame(int frame) {
@ -815,6 +777,13 @@ void Animation::set_frame(int frame) {
this->current_frame_ = this->animation_frame_count_ - abs_frame; this->current_frame_ = this->animation_frame_count_ - abs_frame;
} }
} }
this->update_data_start_();
}
void Animation::update_data_start_() {
const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
} }
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}

View file

@ -123,8 +123,8 @@ class Rect {
void info(const std::string &prefix = "rect info:"); void info(const std::string &prefix = "rect info:");
}; };
class BaseImage;
class Font; class Font;
class Image;
class DisplayBuffer; class DisplayBuffer;
class DisplayPage; class DisplayPage;
class DisplayOnPageChangeTrigger; class DisplayOnPageChangeTrigger;
@ -315,7 +315,7 @@ class DisplayBuffer {
* @param color_on The color to replace in binary images for the on bits. * @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits. * @param color_off The color to replace in binary images for the off bits.
*/ */
void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
#ifdef USE_GRAPH #ifdef USE_GRAPH
/** Draw the `graph` with the top-left corner at [x,y] to the screen. /** Draw the `graph` with the top-left corner at [x,y] to the screen.
@ -529,24 +529,33 @@ class Font {
int height_; int height_;
}; };
class Image { class BaseImage {
public:
virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
virtual int get_width() const = 0;
virtual int get_height() const = 0;
};
class Image : public BaseImage {
public: public:
Image(const uint8_t *data_start, int width, int height, ImageType type); Image(const uint8_t *data_start, int width, int height, ImageType type);
virtual bool get_pixel(int x, int y) const; Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
virtual Color get_color_pixel(int x, int y) const; int get_width() const override;
virtual Color get_rgba_pixel(int x, int y) const; int get_height() const override;
virtual Color get_rgb565_pixel(int x, int y) const;
virtual Color get_grayscale_pixel(int x, int y) const;
int get_width() const;
int get_height() const;
ImageType get_type() const; ImageType get_type() const;
virtual int get_current_frame() const; void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
void set_transparency(bool transparent) { transparent_ = transparent; } void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; } bool has_transparency() const { return transparent_; }
protected: protected:
bool get_binary_pixel_(int x, int y) const;
Color get_rgb24_pixel_(int x, int y) const;
Color get_rgba_pixel_(int x, int y) const;
Color get_rgb565_pixel_(int x, int y) const;
Color get_grayscale_pixel_(int x, int y) const;
int width_; int width_;
int height_; int height_;
ImageType type_; ImageType type_;
@ -557,14 +566,9 @@ class Image {
class Animation : public Image { class Animation : public Image {
public: public:
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
bool get_pixel(int x, int y) const override;
Color get_color_pixel(int x, int y) const override;
Color get_rgba_pixel(int x, int y) const override;
Color get_rgb565_pixel(int x, int y) const override;
Color get_grayscale_pixel(int x, int y) const override;
uint32_t get_animation_frame_count() const; uint32_t get_animation_frame_count() const;
int get_current_frame() const override; int get_current_frame() const;
void next_frame(); void next_frame();
void prev_frame(); void prev_frame();
@ -577,6 +581,9 @@ class Animation : public Image {
void set_loop(uint32_t start_frame, uint32_t end_frame, int count); void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
protected: protected:
void update_data_start_();
const uint8_t *animation_data_start_;
int current_frame_; int current_frame_;
uint32_t animation_frame_count_; uint32_t animation_frame_count_;
uint32_t loop_start_frame_; uint32_t loop_start_frame_;

View file

@ -27,6 +27,7 @@ i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
CONF_MUTE_PIN = "mute_pin" CONF_MUTE_PIN = "mute_pin"
CONF_AUDIO_ID = "audio_id" CONF_AUDIO_ID = "audio_id"
CONF_DAC_TYPE = "dac_type" CONF_DAC_TYPE = "dac_type"
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
INTERNAL_DAC_OPTIONS = { INTERNAL_DAC_OPTIONS = {
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
@ -38,6 +39,8 @@ EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
I2C_COMM_FMT_OPTIONS = ["lsb", "msb"]
def validate_esp32_variant(config): def validate_esp32_variant(config):
if config[CONF_DAC_TYPE] != "internal": if config[CONF_DAC_TYPE] != "internal":
@ -69,6 +72,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MODE, default="mono"): cv.one_of( cv.Optional(CONF_MODE, default="mono"): cv.one_of(
*EXTERNAL_DAC_OPTIONS, lower=True *EXTERNAL_DAC_OPTIONS, lower=True
), ),
cv.Optional(CONF_I2S_COMM_FMT, default="msb"): cv.one_of(
*I2C_COMM_FMT_OPTIONS, lower=True
),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
}, },
@ -94,6 +100,7 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN]) pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN])
cg.add(var.set_mute_pin(pin)) cg.add(var.set_mute_pin(pin))
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb"))
cg.add_library("WiFiClientSecure", None) cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None) cg.add_library("HTTPClient", None)

View file

@ -148,6 +148,7 @@ void I2SAudioMediaPlayer::start_() {
pin_config.data_out_num = this->dout_pin_; pin_config.data_out_num = this->dout_pin_;
i2s_set_pin(this->parent_->get_port(), &pin_config); i2s_set_pin(this->parent_->get_port(), &pin_config);
this->audio_->setI2SCommFMT_LSB(this->i2s_comm_fmt_lsb_);
this->audio_->forceMono(this->external_dac_channels_ == 1); this->audio_->forceMono(this->external_dac_channels_ == 1);
if (this->mute_pin_ != nullptr) { if (this->mute_pin_ != nullptr) {
this->mute_pin_->setup(); this->mute_pin_->setup();

View file

@ -39,6 +39,8 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
#endif #endif
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
void set_i2s_comm_fmt_lsb(bool lsb) { this->i2s_comm_fmt_lsb_ = lsb; }
media_player::MediaPlayerTraits get_traits() override; media_player::MediaPlayerTraits get_traits() override;
bool is_muted() const override { return this->muted_; } bool is_muted() const override { return this->muted_; }
@ -71,6 +73,8 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
#endif #endif
uint8_t external_dac_channels_; uint8_t external_dac_channels_;
bool i2s_comm_fmt_lsb_;
HighFrequencyLoopRequester high_freq_; HighFrequencyLoopRequester high_freq_;
optional<std::string> current_url_{}; optional<std::string> current_url_{};

View file

@ -44,6 +44,7 @@ MODELS = {
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI),
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI),
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI),
"S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI),
} }
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")

View file

@ -421,5 +421,17 @@ void ILI9XXXST7796::initialize() {
} }
} }
// 24_TFT rotated display
void ILI9XXXS3BoxLite::initialize() {
this->init_lcd_(INITCMD_S3BOXLITE);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 240;
}
this->invert_display_(true);
}
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -134,5 +134,10 @@ class ILI9XXXST7796 : public ILI9XXXDisplay {
void initialize() override; void initialize() override;
}; };
class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
protected:
void initialize() override;
};
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -169,6 +169,36 @@ static const uint8_t PROGMEM INITCMD_ST7796[] = {
0x00 // End of list 0x00 // End of list
}; };
static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
0xEF, 3, 0x03, 0x80, 0x02,
0xCF, 3, 0x00, 0xC1, 0x30,
0xED, 4, 0x64, 0x03, 0x12, 0x81,
0xE8, 3, 0x85, 0x00, 0x78,
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
0xF7, 1, 0x20,
0xEA, 2, 0x00, 0x00,
ILI9XXX_PWCTR1 , 1, 0x23, // Power control VRH[5:0]
ILI9XXX_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0]
ILI9XXX_VMCTR1 , 2, 0x3e, 0x28, // VCM control
ILI9XXX_VMCTR2 , 1, 0x86, // VCM control2
ILI9XXX_MADCTL , 1, 0x40, // Memory Access Control
ILI9XXX_VSCRSADD, 1, 0x00, // Vertical scroll zero
ILI9XXX_PIXFMT , 1, 0x55,
ILI9XXX_FRMCTR1 , 2, 0x00, 0x18,
ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable
ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected
ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03,
0x0E, 0x09, 0x00,
ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C,
0x31, 0x36, 0x0F,
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
ILI9XXX_DISPON , 0x80, // Display on
0x00 // End of list
};
// clang-format on // clang-format on
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -199,6 +199,27 @@ IMAGE_SCHEMA = cv.Schema(
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
def load_svg_image(file: str, resize: tuple[int, int]):
from PIL import Image
# This import is only needed in case of SVG images; adding it
# to the top would force configurations not using SVG to also have it
# installed for no reason.
from cairosvg import svg2png
if resize:
req_width, req_height = resize
svg_image = svg2png(
url=file,
output_width=req_width,
output_height=req_height,
)
else:
svg_image = svg2png(url=file)
return Image.open(io.BytesIO(svg_image))
async def to_code(config): async def to_code(config):
from PIL import Image from PIL import Image
@ -206,30 +227,20 @@ async def to_code(config):
if conf_file[CONF_SOURCE] == SOURCE_LOCAL: if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
path = CORE.relative_config_path(conf_file[CONF_PATH]) path = CORE.relative_config_path(conf_file[CONF_PATH])
try:
image = Image.open(path)
except Exception as e:
raise core.EsphomeError(f"Could not load image file {path}: {e}")
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
elif conf_file[CONF_SOURCE] == SOURCE_MDI: elif conf_file[CONF_SOURCE] == SOURCE_MDI:
# Those imports are only needed in case of MDI images; adding them path = _compute_local_icon_path(conf_file).as_posix()
# to the top would force configurations not using MDI to also have them
# installed for no reason.
from cairosvg import svg2png
svg_file = _compute_local_icon_path(conf_file) try:
if CONF_RESIZE in config: resize = config.get(CONF_RESIZE)
req_width, req_height = config[CONF_RESIZE] if path.lower().endswith(".svg"):
svg_image = svg2png( image = load_svg_image(path, resize)
url=svg_file.as_posix(),
output_width=req_width,
output_height=req_height,
)
else: else:
svg_image = svg2png(url=svg_file.as_posix()) image = Image.open(path)
if resize:
image = Image.open(io.BytesIO(svg_image)) image.thumbnail(resize)
except Exception as e:
raise core.EsphomeError(f"Could not load image file {path}: {e}")
width, height = image.size width, height = image.size

View file

@ -278,7 +278,9 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
} }
} }
} else { } else {
b = pixels[7 - col]; for (uint8_t i = 0; i < 8; i++) {
b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
}
} }
// send this byte to display at selected chip // send this byte to display at selected chip
if (this->invert_) { if (this->invert_) {

View file

@ -6,6 +6,7 @@ from esphome.const import CONF_ID
AUTO_LOAD = ["pn532"] AUTO_LOAD = ["pn532"]
CODEOWNERS = ["@OttoWinter", "@jesserockz"] CODEOWNERS = ["@OttoWinter", "@jesserockz"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
MULTI_CONF = True
pn532_i2c_ns = cg.esphome_ns.namespace("pn532_i2c") pn532_i2c_ns = cg.esphome_ns.namespace("pn532_i2c")
PN532I2C = pn532_i2c_ns.class_("PN532I2C", pn532.PN532, i2c.I2CDevice) PN532I2C = pn532_i2c_ns.class_("PN532I2C", pn532.PN532, i2c.I2CDevice)

View file

@ -0,0 +1,123 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import (
binary_sensor,
alarm_control_panel,
)
from esphome.const import (
CONF_ID,
CONF_BINARY_SENSORS,
CONF_INPUT,
CONF_RESTORE_MODE,
)
from .. import template_ns
CODEOWNERS = ["@grahambrown11"]
CONF_CODES = "codes"
CONF_BYPASS_ARMED_HOME = "bypass_armed_home"
CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm"
CONF_ARMING_HOME_TIME = "arming_home_time"
CONF_ARMING_AWAY_TIME = "arming_away_time"
CONF_PENDING_TIME = "pending_time"
CONF_TRIGGER_TIME = "trigger_time"
FLAG_NORMAL = "normal"
FLAG_BYPASS_ARMED_HOME = "bypass_armed_home"
BinarySensorFlags = {
FLAG_NORMAL: 1 << 0,
FLAG_BYPASS_ARMED_HOME: 1 << 1,
}
TemplateAlarmControlPanel = template_ns.class_(
"TemplateAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component
)
TemplateAlarmControlPanelRestoreMode = template_ns.enum(
"TemplateAlarmControlPanelRestoreMode"
)
RESTORE_MODES = {
"ALWAYS_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_ALWAYS_DISARMED,
"RESTORE_DEFAULT_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED,
}
def validate_config(config):
if config.get(CONF_REQUIRES_CODE_TO_ARM, False) and not config.get(CONF_CODES, []):
raise cv.Invalid(
f"{CONF_REQUIRES_CODE_TO_ARM} cannot be True when there are no codes."
)
return config
TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA = cv.maybe_simple_value(
{
cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean,
},
key=CONF_INPUT,
)
TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA = (
alarm_control_panel.ALARM_CONTROL_PANEL_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TemplateAlarmControlPanel),
cv.Optional(CONF_CODES): cv.ensure_list(cv.string_strict),
cv.Optional(CONF_REQUIRES_CODE_TO_ARM): cv.boolean,
cv.Optional(CONF_ARMING_HOME_TIME): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_ARMING_AWAY_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_PENDING_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_TRIGGER_TIME, default="0s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
TEMPLATE_ALARM_CONTROL_PANEL_BINARY_SENSOR_SCHEMA
),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_DISARMED"): cv.enum(
RESTORE_MODES, upper=True
),
}
).extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.All(
TEMPLATE_ALARM_CONTROL_PANEL_SCHEMA,
validate_config,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await alarm_control_panel.register_alarm_control_panel(var, config)
if CONF_CODES in config:
for acode in config[CONF_CODES]:
cg.add(var.add_code(acode))
if CONF_REQUIRES_CODE_TO_ARM in config:
cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM]))
cg.add(var.set_arming_away_time(config[CONF_ARMING_AWAY_TIME]))
cg.add(var.set_pending_time(config[CONF_PENDING_TIME]))
cg.add(var.set_trigger_time(config[CONF_TRIGGER_TIME]))
supports_arm_home = False
if CONF_ARMING_HOME_TIME in config:
cg.add(var.set_arming_home_time(config[CONF_ARMING_HOME_TIME]))
supports_arm_home = True
for sensor in config.get(CONF_BINARY_SENSORS, []):
bs = await cg.get_variable(sensor[CONF_INPUT])
flags = BinarySensorFlags[FLAG_NORMAL]
if sensor[CONF_BYPASS_ARMED_HOME]:
flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME]
supports_arm_home = True
cg.add(var.add_sensor(bs, flags))
cg.add(var.set_supports_arm_home(supports_arm_home))
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))

View file

@ -0,0 +1,180 @@
#include "template_alarm_control_panel.h"
#include <utility>
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace template_ {
using namespace esphome::alarm_control_panel;
static const char *const TAG = "template.alarm_control_panel";
TemplateAlarmControlPanel::TemplateAlarmControlPanel(){};
#ifdef USE_BINARY_SENSOR
void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags) {
this->sensor_map_[sensor] = flags;
};
#endif
void TemplateAlarmControlPanel::dump_config() {
ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:");
ESP_LOGCONFIG(TAG, " Current State: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)));
ESP_LOGCONFIG(TAG, " Number of Codes: %u", this->codes_.size());
if (!this->codes_.empty())
ESP_LOGCONFIG(TAG, " Requires Code To Arm: %s", YESNO(this->requires_code_to_arm_));
ESP_LOGCONFIG(TAG, " Arming Away Time: %us", (this->arming_away_time_ / 1000));
if (this->arming_home_time_ != 0)
ESP_LOGCONFIG(TAG, " Arming Home Time: %us", (this->arming_home_time_ / 1000));
ESP_LOGCONFIG(TAG, " Pending Time: %us", (this->pending_time_ / 1000));
ESP_LOGCONFIG(TAG, " Trigger Time: %us", (this->trigger_time_ / 1000));
ESP_LOGCONFIG(TAG, " Supported Features: %u", this->get_supported_features());
#ifdef USE_BINARY_SENSOR
for (auto sensor_pair : this->sensor_map_) {
ESP_LOGCONFIG(TAG, " Binary Sesnsor:");
ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str());
ESP_LOGCONFIG(TAG, " Armed home bypass: %s",
TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME));
}
#endif
}
void TemplateAlarmControlPanel::setup() {
ESP_LOGCONFIG(TAG, "Setting up Template AlarmControlPanel '%s'...", this->name_.c_str());
switch (this->restore_mode_) {
case ALARM_CONTROL_PANEL_ALWAYS_DISARMED:
this->current_state_ = ACP_STATE_DISARMED;
break;
case ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED: {
uint8_t value;
this->pref_ = global_preferences->make_preference<uint8_t>(this->get_object_id_hash());
if (this->pref_.load(&value)) {
this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value);
} else {
this->current_state_ = ACP_STATE_DISARMED;
}
break;
}
}
this->desired_state_ = this->current_state_;
}
void TemplateAlarmControlPanel::loop() {
// change from ARMING to ARMED_x after the arming_time_ has passed
if (this->current_state_ == ACP_STATE_ARMING) {
auto delay = this->arming_away_time_;
if (this->desired_state_ == ACP_STATE_ARMED_HOME) {
delay = this->arming_home_time_;
}
if ((millis() - this->last_update_) > delay) {
this->publish_state(this->desired_state_);
}
return;
}
// change from PENDING to TRIGGERED after the delay_time_ has passed
if (this->current_state_ == ACP_STATE_PENDING && (millis() - this->last_update_) > this->pending_time_) {
this->publish_state(ACP_STATE_TRIGGERED);
return;
}
auto future_state = this->current_state_;
// reset triggered if all clear
if (this->current_state_ == ACP_STATE_TRIGGERED && this->trigger_time_ > 0 &&
(millis() - this->last_update_) > this->trigger_time_) {
future_state = this->desired_state_;
}
bool trigger = false;
#ifdef USE_BINARY_SENSOR
if (this->is_state_armed(future_state)) {
// TODO might be better to register change for each sensor in setup...
for (auto sensor_pair : this->sensor_map_) {
if (sensor_pair.first->state) {
if (this->current_state_ == ACP_STATE_ARMED_HOME &&
(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
continue;
}
trigger = true;
break;
}
}
}
#endif
if (trigger) {
if (this->pending_time_ > 0 && this->current_state_ != ACP_STATE_TRIGGERED) {
this->publish_state(ACP_STATE_PENDING);
} else {
this->publish_state(ACP_STATE_TRIGGERED);
}
} else if (future_state != this->current_state_) {
this->publish_state(future_state);
}
}
bool TemplateAlarmControlPanel::is_code_valid_(optional<std::string> code) {
if (!this->codes_.empty()) {
if (code.has_value()) {
ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str());
return (std::count(this->codes_.begin(), this->codes_.end(), code.value()) == 1);
}
ESP_LOGD(TAG, "No code provided");
return false;
}
return true;
}
uint32_t TemplateAlarmControlPanel::get_supported_features() const {
uint32_t features = ACP_FEAT_ARM_AWAY | ACP_FEAT_TRIGGER;
if (this->supports_arm_home_) {
features |= ACP_FEAT_ARM_HOME;
}
return features;
}
bool TemplateAlarmControlPanel::get_requires_code() const { return !this->codes_.empty(); }
void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_panel::AlarmControlPanelState state,
uint32_t delay) {
if (this->current_state_ != ACP_STATE_DISARMED) {
ESP_LOGW(TAG, "Cannot arm when not disarmed");
return;
}
if (this->requires_code_to_arm_ && !this->is_code_valid_(std::move(code))) {
ESP_LOGW(TAG, "Not arming code doesn't match");
return;
}
this->desired_state_ = state;
if (delay > 0) {
this->publish_state(ACP_STATE_ARMING);
} else {
this->publish_state(state);
}
}
void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) {
if (call.get_state()) {
if (call.get_state() == ACP_STATE_ARMED_AWAY) {
this->arm_(call.get_code(), ACP_STATE_ARMED_AWAY, this->arming_away_time_);
} else if (call.get_state() == ACP_STATE_ARMED_HOME) {
this->arm_(call.get_code(), ACP_STATE_ARMED_HOME, this->arming_home_time_);
} else if (call.get_state() == ACP_STATE_DISARMED) {
if (!this->is_code_valid_(call.get_code())) {
ESP_LOGW(TAG, "Not disarming code doesn't match");
return;
}
this->desired_state_ = ACP_STATE_DISARMED;
this->publish_state(ACP_STATE_DISARMED);
} else if (call.get_state() == ACP_STATE_TRIGGERED) {
this->publish_state(ACP_STATE_TRIGGERED);
} else if (call.get_state() == ACP_STATE_PENDING) {
this->publish_state(ACP_STATE_PENDING);
} else {
ESP_LOGE(TAG, "State not yet implemented: %s",
LOG_STR_ARG(alarm_control_panel_state_to_string(*call.get_state())));
}
}
}
} // namespace template_
} // namespace esphome

View file

@ -0,0 +1,116 @@
#pragma once
#include <map>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
namespace esphome {
namespace template_ {
#ifdef USE_BINARY_SENSOR
enum BinarySensorFlags : uint16_t {
BINARY_SENSOR_MODE_NORMAL = 1 << 0,
BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1,
};
#endif
enum TemplateAlarmControlPanelRestoreMode {
ALARM_CONTROL_PANEL_ALWAYS_DISARMED,
ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED,
};
class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component {
public:
TemplateAlarmControlPanel();
void dump_config() override;
void setup() override;
void loop() override;
uint32_t get_supported_features() const override;
bool get_requires_code() const override;
bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; }
void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
#ifdef USE_BINARY_SENSOR
/** Add a binary_sensor to the alarm_panel.
*
* @param sensor The BinarySensor instance.
* @param ignore_when_home if this should be ignored when armed_home mode
*/
void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0);
#endif
/** add a code
*
* @param code The code
*/
void add_code(const std::string &code) { this->codes_.push_back(code); }
/** set requires a code to arm
*
* @param code_to_arm The requires code to arm
*/
void set_requires_code_to_arm(bool code_to_arm) { this->requires_code_to_arm_ = code_to_arm; }
/** set the delay before arming away
*
* @param time The milliseconds
*/
void set_arming_away_time(uint32_t time) { this->arming_away_time_ = time; }
/** set the delay before arming home
*
* @param time The milliseconds
*/
void set_arming_home_time(uint32_t time) { this->arming_home_time_ = time; }
/** set the delay before triggering
*
* @param time The milliseconds
*/
void set_pending_time(uint32_t time) { this->pending_time_ = time; }
/** set the delay before resetting after triggered
*
* @param time The milliseconds
*/
void set_trigger_time(uint32_t time) { this->trigger_time_ = time; }
void set_supports_arm_home(bool supports_arm_home) { supports_arm_home_ = supports_arm_home; }
protected:
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
#ifdef USE_BINARY_SENSOR
// the map of binary sensors that the alarm_panel monitors with their modes
std::map<binary_sensor::BinarySensor *, uint16_t> sensor_map_;
#endif
TemplateAlarmControlPanelRestoreMode restore_mode_{};
// the arming away delay
uint32_t arming_away_time_;
// the arming home delay
uint32_t arming_home_time_{0};
// the trigger delay
uint32_t pending_time_;
// the time in trigger
uint32_t trigger_time_;
// a list of codes
std::vector<std::string> codes_;
// requires a code to arm
bool requires_code_to_arm_ = false;
bool supports_arm_home_ = false;
// check if the code is valid
bool is_code_valid_(optional<std::string> code);
void arm_(optional<std::string> code, alarm_control_panel::AlarmControlPanelState state, uint32_t delay);
};
} // namespace template_
} // namespace esphome

View file

@ -15,6 +15,7 @@ VBus = vbus_ns.class_("VBus", uart.UARTDevice, cg.Component)
CONF_VBUS_ID = "vbus_id" CONF_VBUS_ID = "vbus_id"
CONF_DELTASOL_BS_PLUS = "deltasol_bs_plus" CONF_DELTASOL_BS_PLUS = "deltasol_bs_plus"
CONF_DELTASOL_BS_2009 = "deltasol_bs_2009"
CONF_DELTASOL_C = "deltasol_c" CONF_DELTASOL_C = "deltasol_c"
CONF_DELTASOL_CS2 = "deltasol_cs2" CONF_DELTASOL_CS2 = "deltasol_cs2"
CONF_DELTASOL_CS_PLUS = "deltasol_cs_plus" CONF_DELTASOL_CS_PLUS = "deltasol_cs_plus"

View file

@ -18,12 +18,14 @@ from .. import (
VBus, VBus,
CONF_VBUS_ID, CONF_VBUS_ID,
CONF_DELTASOL_BS_PLUS, CONF_DELTASOL_BS_PLUS,
CONF_DELTASOL_BS_2009,
CONF_DELTASOL_C, CONF_DELTASOL_C,
CONF_DELTASOL_CS2, CONF_DELTASOL_CS2,
CONF_DELTASOL_CS_PLUS, CONF_DELTASOL_CS_PLUS,
) )
DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusBSensor", cg.Component) DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusBSensor", cg.Component)
DeltaSol_BS_2009 = vbus_ns.class_("DeltaSolBS2009BSensor", cg.Component)
DeltaSol_C = vbus_ns.class_("DeltaSolCBSensor", cg.Component) DeltaSol_C = vbus_ns.class_("DeltaSolCBSensor", cg.Component)
DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2BSensor", cg.Component) DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2BSensor", cg.Component)
DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusBSensor", cg.Component) DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusBSensor", cg.Component)
@ -42,6 +44,7 @@ CONF_COLLECTOR_FROST = "collector_frost"
CONF_TUBE_COLLECTOR = "tube_collector" CONF_TUBE_COLLECTOR = "tube_collector"
CONF_RECOOLING = "recooling" CONF_RECOOLING = "recooling"
CONF_HQM = "hqm" CONF_HQM = "hqm"
CONF_FROST_PROTECTION_ACTIVE = "frost_protection_active"
CONFIG_SCHEMA = cv.typed_schema( CONFIG_SCHEMA = cv.typed_schema(
{ {
@ -87,6 +90,33 @@ CONFIG_SCHEMA = cv.typed_schema(
), ),
} }
), ),
CONF_DELTASOL_BS_2009: cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DeltaSol_BS_2009),
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_PROBLEM,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_PROBLEM,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_PROBLEM,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_PROBLEM,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(
CONF_FROST_PROTECTION_ACTIVE
): binary_sensor.binary_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
),
CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend( CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(DeltaSol_C), cv.GenerateID(): cv.declare_id(DeltaSol_C),
@ -222,6 +252,28 @@ async def to_code(config):
sens = await binary_sensor.new_binary_sensor(config[CONF_HQM]) sens = await binary_sensor.new_binary_sensor(config[CONF_HQM])
cg.add(var.set_hqm_bsensor(sens)) cg.add(var.set_hqm_bsensor(sens))
elif config[CONF_MODEL] == CONF_DELTASOL_BS_2009:
cg.add(var.set_command(0x0100))
cg.add(var.set_source(0x427B))
cg.add(var.set_dest(0x0010))
if CONF_SENSOR1_ERROR in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR])
cg.add(var.set_s1_error_bsensor(sens))
if CONF_SENSOR2_ERROR in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR])
cg.add(var.set_s2_error_bsensor(sens))
if CONF_SENSOR3_ERROR in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR])
cg.add(var.set_s3_error_bsensor(sens))
if CONF_SENSOR4_ERROR in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR])
cg.add(var.set_s4_error_bsensor(sens))
if CONF_FROST_PROTECTION_ACTIVE in config:
sens = await binary_sensor.new_binary_sensor(
config[CONF_FROST_PROTECTION_ACTIVE]
)
cg.add(var.set_frost_protection_active_bsensor(sens))
elif config[CONF_MODEL] == CONF_DELTASOL_C: elif config[CONF_MODEL] == CONF_DELTASOL_C:
cg.add(var.set_command(0x0100)) cg.add(var.set_command(0x0100))
cg.add(var.set_source(0x4212)) cg.add(var.set_source(0x4212))

View file

@ -50,6 +50,28 @@ void DeltaSolBSPlusBSensor::handle_message(std::vector<uint8_t> &message) {
this->hqm_bsensor_->publish_state(message[15] & 0x20); this->hqm_bsensor_->publish_state(message[15] & 0x20);
} }
void DeltaSolBS2009BSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Deltasol BS 2009:");
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);
LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_);
LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_);
LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_);
LOG_BINARY_SENSOR(" ", "Frost Protection Active", this->frost_protection_active_bsensor_);
}
void DeltaSolBS2009BSensor::handle_message(std::vector<uint8_t> &message) {
if (this->s1_error_bsensor_ != nullptr)
this->s1_error_bsensor_->publish_state(message[20] & 1);
if (this->s2_error_bsensor_ != nullptr)
this->s2_error_bsensor_->publish_state(message[20] & 2);
if (this->s3_error_bsensor_ != nullptr)
this->s3_error_bsensor_->publish_state(message[20] & 4);
if (this->s4_error_bsensor_ != nullptr)
this->s4_error_bsensor_->publish_state(message[20] & 8);
if (this->frost_protection_active_bsensor_ != nullptr)
this->frost_protection_active_bsensor_->publish_state(message[25] & 1);
}
void DeltaSolCBSensor::dump_config() { void DeltaSolCBSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Deltasol C:"); ESP_LOGCONFIG(TAG, "Deltasol C:");
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_); LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);

View file

@ -39,6 +39,27 @@ class DeltaSolBSPlusBSensor : public VBusListener, public Component {
void handle_message(std::vector<uint8_t> &message) override; void handle_message(std::vector<uint8_t> &message) override;
}; };
class DeltaSolBS2009BSensor : public VBusListener, public Component {
public:
void dump_config() override;
void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; }
void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; }
void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; }
void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; }
void set_frost_protection_active_bsensor(binary_sensor::BinarySensor *bsensor) {
this->frost_protection_active_bsensor_ = bsensor;
}
protected:
binary_sensor::BinarySensor *s1_error_bsensor_{nullptr};
binary_sensor::BinarySensor *s2_error_bsensor_{nullptr};
binary_sensor::BinarySensor *s3_error_bsensor_{nullptr};
binary_sensor::BinarySensor *s4_error_bsensor_{nullptr};
binary_sensor::BinarySensor *frost_protection_active_bsensor_{nullptr};
void handle_message(std::vector<uint8_t> &message) override;
};
class DeltaSolCBSensor : public VBusListener, public Component { class DeltaSolCBSensor : public VBusListener, public Component {
public: public:
void dump_config() override; void dump_config() override;

View file

@ -33,12 +33,14 @@ from .. import (
VBus, VBus,
CONF_VBUS_ID, CONF_VBUS_ID,
CONF_DELTASOL_BS_PLUS, CONF_DELTASOL_BS_PLUS,
CONF_DELTASOL_BS_2009,
CONF_DELTASOL_C, CONF_DELTASOL_C,
CONF_DELTASOL_CS2, CONF_DELTASOL_CS2,
CONF_DELTASOL_CS_PLUS, CONF_DELTASOL_CS_PLUS,
) )
DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusSensor", cg.Component) DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusSensor", cg.Component)
DeltaSol_BS_2009 = vbus_ns.class_("DeltaSolBS2009Sensor", cg.Component)
DeltaSol_C = vbus_ns.class_("DeltaSolCSensor", cg.Component) DeltaSol_C = vbus_ns.class_("DeltaSolCSensor", cg.Component)
DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2Sensor", cg.Component) DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2Sensor", cg.Component)
DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusSensor", cg.Component) DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusSensor", cg.Component)
@ -142,6 +144,87 @@ CONFIG_SCHEMA = cv.typed_schema(
), ),
} }
), ),
CONF_DELTASOL_BS_2009: cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DeltaSol_BS_2009),
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PUMP_SPEED_1): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PUMP_SPEED_2): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_OPERATING_HOURS_1): sensor.sensor_schema(
unit_of_measurement=UNIT_HOUR,
icon=ICON_TIMER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_OPERATING_HOURS_2): sensor.sensor_schema(
unit_of_measurement=UNIT_HOUR,
icon=ICON_TIMER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TIME): sensor.sensor_schema(
unit_of_measurement=UNIT_MINUTE,
icon=ICON_TIMER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DURATION,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_VERSION): sensor.sensor_schema(
accuracy_decimals=2,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
),
CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend( CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(DeltaSol_C), cv.GenerateID(): cv.declare_id(DeltaSol_C),
@ -437,6 +520,44 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_VERSION]) sens = await sensor.new_sensor(config[CONF_VERSION])
cg.add(var.set_version_sensor(sens)) cg.add(var.set_version_sensor(sens))
elif config[CONF_MODEL] == CONF_DELTASOL_BS_2009:
cg.add(var.set_command(0x0100))
cg.add(var.set_source(0x427B))
cg.add(var.set_dest(0x0010))
if CONF_TEMPERATURE_1 in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1])
cg.add(var.set_temperature1_sensor(sens))
if CONF_TEMPERATURE_2 in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2])
cg.add(var.set_temperature2_sensor(sens))
if CONF_TEMPERATURE_3 in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3])
cg.add(var.set_temperature3_sensor(sens))
if CONF_TEMPERATURE_4 in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4])
cg.add(var.set_temperature4_sensor(sens))
if CONF_PUMP_SPEED_1 in config:
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_1])
cg.add(var.set_pump_speed1_sensor(sens))
if CONF_PUMP_SPEED_2 in config:
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_2])
cg.add(var.set_pump_speed2_sensor(sens))
if CONF_OPERATING_HOURS_1 in config:
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_1])
cg.add(var.set_operating_hours1_sensor(sens))
if CONF_OPERATING_HOURS_2 in config:
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_2])
cg.add(var.set_operating_hours2_sensor(sens))
if CONF_HEAT_QUANTITY in config:
sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY])
cg.add(var.set_heat_quantity_sensor(sens))
if CONF_TIME in config:
sens = await sensor.new_sensor(config[CONF_TIME])
cg.add(var.set_time_sensor(sens))
if CONF_VERSION in config:
sens = await sensor.new_sensor(config[CONF_VERSION])
cg.add(var.set_version_sensor(sens))
elif config[CONF_MODEL] == CONF_DELTASOL_C: elif config[CONF_MODEL] == CONF_DELTASOL_C:
cg.add(var.set_command(0x0100)) cg.add(var.set_command(0x0100))
cg.add(var.set_source(0x4212)) cg.add(var.set_source(0x4212))

View file

@ -57,6 +57,47 @@ void DeltaSolBSPlusSensor::handle_message(std::vector<uint8_t> &message) {
this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f); this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f);
} }
void DeltaSolBS2009Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "Deltasol BS 2009:");
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_);
LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_);
LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_);
LOG_SENSOR(" ", "Pump Speed 1", this->pump_speed1_sensor_);
LOG_SENSOR(" ", "Pump Speed 2", this->pump_speed2_sensor_);
LOG_SENSOR(" ", "Operating Hours 1", this->operating_hours1_sensor_);
LOG_SENSOR(" ", "Operating Hours 2", this->operating_hours2_sensor_);
LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_);
LOG_SENSOR(" ", "System Time", this->time_sensor_);
LOG_SENSOR(" ", "FW Version", this->version_sensor_);
}
void DeltaSolBS2009Sensor::handle_message(std::vector<uint8_t> &message) {
if (this->temperature1_sensor_ != nullptr)
this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f);
if (this->temperature2_sensor_ != nullptr)
this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f);
if (this->temperature3_sensor_ != nullptr)
this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f);
if (this->temperature4_sensor_ != nullptr)
this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f);
if (this->pump_speed1_sensor_ != nullptr)
this->pump_speed1_sensor_->publish_state(message[8]);
if (this->pump_speed2_sensor_ != nullptr)
this->pump_speed2_sensor_->publish_state(message[12]);
if (this->operating_hours1_sensor_ != nullptr)
this->operating_hours1_sensor_->publish_state(get_u16(message, 10));
if (this->operating_hours2_sensor_ != nullptr)
this->operating_hours2_sensor_->publish_state(get_u16(message, 18));
if (this->heat_quantity_sensor_ != nullptr) {
this->heat_quantity_sensor_->publish_state(get_u16(message, 28) + get_u16(message, 30) * 1000);
}
if (this->time_sensor_ != nullptr)
this->time_sensor_->publish_state(get_u16(message, 22));
if (this->version_sensor_ != nullptr)
this->version_sensor_->publish_state(get_u16(message, 32) * 0.01f);
}
void DeltaSolCSensor::dump_config() { void DeltaSolCSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Deltasol C:"); ESP_LOGCONFIG(TAG, "Deltasol C:");
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_); LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
@ -166,9 +207,9 @@ void DeltaSolCSPlusSensor::handle_message(std::vector<uint8_t> &message) {
if (this->heat_quantity_sensor_ != nullptr) if (this->heat_quantity_sensor_ != nullptr)
this->heat_quantity_sensor_->publish_state((get_u16(message, 30) << 16) + get_u16(message, 28)); this->heat_quantity_sensor_->publish_state((get_u16(message, 30) << 16) + get_u16(message, 28));
if (this->time_sensor_ != nullptr) if (this->time_sensor_ != nullptr)
this->time_sensor_->publish_state(get_u16(message, 12)); this->time_sensor_->publish_state(get_u16(message, 22));
if (this->version_sensor_ != nullptr) if (this->version_sensor_ != nullptr)
this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f); this->version_sensor_->publish_state(get_u16(message, 32) * 0.01f);
if (this->flow_rate_sensor_ != nullptr) if (this->flow_rate_sensor_ != nullptr)
this->flow_rate_sensor_->publish_state(get_u16(message, 38)); this->flow_rate_sensor_->publish_state(get_u16(message, 38));
} }

View file

@ -37,6 +37,37 @@ class DeltaSolBSPlusSensor : public VBusListener, public Component {
void handle_message(std::vector<uint8_t> &message) override; void handle_message(std::vector<uint8_t> &message) override;
}; };
class DeltaSolBS2009Sensor : public VBusListener, public Component {
public:
void dump_config() override;
void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; }
void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; }
void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; }
void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; }
void set_pump_speed1_sensor(sensor::Sensor *sensor) { this->pump_speed1_sensor_ = sensor; }
void set_pump_speed2_sensor(sensor::Sensor *sensor) { this->pump_speed2_sensor_ = sensor; }
void set_operating_hours1_sensor(sensor::Sensor *sensor) { this->operating_hours1_sensor_ = sensor; }
void set_operating_hours2_sensor(sensor::Sensor *sensor) { this->operating_hours2_sensor_ = sensor; }
void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; }
void set_time_sensor(sensor::Sensor *sensor) { this->time_sensor_ = sensor; }
void set_version_sensor(sensor::Sensor *sensor) { this->version_sensor_ = sensor; }
protected:
sensor::Sensor *temperature1_sensor_{nullptr};
sensor::Sensor *temperature2_sensor_{nullptr};
sensor::Sensor *temperature3_sensor_{nullptr};
sensor::Sensor *temperature4_sensor_{nullptr};
sensor::Sensor *pump_speed1_sensor_{nullptr};
sensor::Sensor *pump_speed2_sensor_{nullptr};
sensor::Sensor *operating_hours1_sensor_{nullptr};
sensor::Sensor *operating_hours2_sensor_{nullptr};
sensor::Sensor *heat_quantity_sensor_{nullptr};
sensor::Sensor *time_sensor_{nullptr};
sensor::Sensor *version_sensor_{nullptr};
void handle_message(std::vector<uint8_t> &message) override;
};
class DeltaSolCSensor : public VBusListener, public Component { class DeltaSolCSensor : public VBusListener, public Component {
public: public:
void dump_config() override; void dump_config() override;

View file

@ -91,6 +91,16 @@ bool ListEntitiesIterator::on_select(select::Select *select) {
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->web_server_->events_.send(
this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL)
.c_str(),
"state");
return true;
}
#endif
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome

View file

@ -49,6 +49,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_LOCK #ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override; bool on_lock(lock::Lock *a_lock) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
protected: protected:
WebServer *web_server_; WebServer *web_server_;

View file

@ -813,6 +813,9 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
} }
#endif #endif
// Longest: HORIZONTAL
#define PSTR_LOCAL(mode_s) strncpy_P(buf, (PGM_P) ((mode_s)), 15)
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
void WebServer::on_climate_update(climate::Climate *obj) { void WebServer::on_climate_update(climate::Climate *obj) {
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
@ -869,16 +872,13 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
request->send(404); request->send(404);
} }
// Longest: HORIZONTAL
#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P) ((mode_s)), 15)
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) { return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits(); const auto traits = obj->get_traits();
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
char __buf[16]; char buf[16];
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("modes"); JsonArray opt = root.createNestedArray("modes");
@ -996,6 +996,34 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
}
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value,
JsonDetail start_config) {
return json::build_json([obj, value, start_config](JsonObject root) {
char buf[16];
set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
});
}
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
}
}
request->send(404);
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) { bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/") if (request->url() == "/")
return true; return true;
@ -1073,6 +1101,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true; return true;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
if (request->method() == HTTP_GET && match.domain == "alarm_control_panel")
return true;
#endif
return false; return false;
} }
void WebServer::handleRequest(AsyncWebServerRequest *request) { void WebServer::handleRequest(AsyncWebServerRequest *request) {
@ -1180,6 +1213,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return; return;
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
if (match.domain == "alarm_control_panel") {
this->handle_alarm_control_panel_request(request, match);
return;
}
#endif
} }
bool WebServer::isRequestHandlerTrivial() { return false; } bool WebServer::isRequestHandlerTrivial() { return false; }

View file

@ -216,6 +216,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
/// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the alarm_control_panel state with its value as a JSON string.
std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method. /// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override; bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method. /// Override the web handler's handleRequest method.

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2023.6.0-dev" __version__ = "2023.7.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (

View file

@ -48,6 +48,9 @@
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h" #include "esphome/components/media_player/media_player.h"
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
namespace esphome { namespace esphome {
@ -126,6 +129,12 @@ class Application {
void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); } void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->alarm_control_panels_.push_back(a_alarm_control_panel);
}
#endif
/// Register the component in this Application instance. /// Register the component in this Application instance.
template<class C> C *register_component(C *c) { template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered"); static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@ -296,6 +305,18 @@ class Application {
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
const std::vector<alarm_control_panel::AlarmControlPanel *> &get_alarm_control_panels() {
return this->alarm_control_panels_;
}
alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->alarm_control_panels_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler; Scheduler scheduler;
protected: protected:
@ -349,6 +370,9 @@ class Application {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
std::vector<media_player::MediaPlayer *> media_players_{}; std::vector<media_player::MediaPlayer *> media_players_{};
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
std::vector<alarm_control_panel::AlarmControlPanel *> alarm_control_panels_{};
#endif
std::string name_; std::string name_;
std::string friendly_name_; std::string friendly_name_;

View file

@ -246,6 +246,21 @@ void ComponentIterator::advance() {
} }
} }
break; break;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
case IteratorState::ALARM_CONTROL_PANEL:
if (this->at_ >= App.get_alarm_control_panels().size()) {
advance_platform = true;
} else {
auto *a_alarm_control_panel = App.get_alarm_control_panels()[this->at_];
if (a_alarm_control_panel->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_alarm_control_panel(a_alarm_control_panel);
}
}
break;
#endif #endif
case IteratorState::MAX: case IteratorState::MAX:
if (this->on_end()) { if (this->on_end()) {

View file

@ -65,6 +65,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
virtual bool on_media_player(media_player::MediaPlayer *media_player); virtual bool on_media_player(media_player::MediaPlayer *media_player);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0;
#endif #endif
virtual bool on_end(); virtual bool on_end();
@ -116,6 +119,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
MEDIA_PLAYER, MEDIA_PLAYER,
#endif
#ifdef USE_ALARM_CONTROL_PANEL
ALARM_CONTROL_PANEL,
#endif #endif
MAX, MAX,
} state_{IteratorState::NONE}; } state_{IteratorState::NONE};

View file

@ -79,6 +79,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_media_player_update(obj); }); obj->add_on_state_callback([this, obj]() { this->on_media_player_update(obj); });
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
for (auto *obj : App.get_alarm_control_panels()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); });
}
#endif
} }
} // namespace esphome } // namespace esphome

View file

@ -40,6 +40,9 @@
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h" #include "esphome/components/media_player/media_player.h"
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
namespace esphome { namespace esphome {
@ -82,6 +85,9 @@ class Controller {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
virtual void on_media_player_update(media_player::MediaPlayer *obj){}; virtual void on_media_player_update(media_player::MediaPlayer *obj){};
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){};
#endif
}; };
} // namespace esphome } // namespace esphome

View file

@ -17,6 +17,7 @@
#define USE_API #define USE_API
#define USE_API_NOISE #define USE_API_NOISE
#define USE_API_PLAINTEXT #define USE_API_PLAINTEXT
#define USE_ALARM_CONTROL_PANEL
#define USE_BINARY_SENSOR #define USE_BINARY_SENSOR
#define USE_BUTTON #define USE_BUTTON
#define USE_CLIMATE #define USE_CLIMATE

View file

@ -11,3 +11,5 @@ pytest-mock==3.10.0
pytest-asyncio==0.21.0 pytest-asyncio==0.21.0
asyncmock==0.4.2 asyncmock==0.4.2
hypothesis==5.49.0 hypothesis==5.49.0
clang-format==13.0.1 ; platform_machine != 'armv7l'

View file

@ -18,6 +18,7 @@ will be generated, they still need to be formatted
""" """
import re import re
import os
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from subprocess import call from subprocess import call
@ -944,3 +945,19 @@ with open(root / "api_pb2_service.cpp", "w") as f:
f.write(cpp) f.write(cpp)
prot.unlink() prot.unlink()
try:
import clang_format
def exec_clang_format(path):
clang_format_path = os.path.join(
os.path.dirname(clang_format.__file__), "data", "bin", "clang-format"
)
call([clang_format_path, "-i", path])
exec_clang_format(root / "api_pb2_service.h")
exec_clang_format(root / "api_pb2_service.cpp")
exec_clang_format(root / "api_pb2.h")
exec_clang_format(root / "api_pb2.cpp")
except ImportError:
pass

View file

@ -3417,3 +3417,21 @@ lcd_menu:
on_prev: on_prev:
then: then:
lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());'
alarm_control_panel:
- platform: template
id: alarmcontrolpanel1
name: Alarm Panel
codes:
- "1234"
requires_code_to_arm: true
arming_home_time: 1s
arming_away_time: 15s
pending_time: 15s
trigger_time: 30s
binary_sensors:
- binary_sensor1
on_state:
then:
- lambda: !lambda |-
ESP_LOGD("TEST", "State change %s", alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()));

View file

@ -822,7 +822,6 @@ switch:
name: R0 Switch name: R0 Switch
component_name: page0.r0 component_name: page0.r0
climate: climate:
- platform: bang_bang - platform: bang_bang
name: Bang Bang Climate name: Bang Bang Climate
@ -1106,7 +1105,6 @@ rf_bridge:
- rf_bridge.send_raw: - rf_bridge.send_raw:
raw: "AAA5070008001000ABC12355" raw: "AAA5070008001000ABC12355"
display: display:
- platform: nextion - platform: nextion
uart_id: uart_1 uart_id: uart_1
@ -1171,3 +1169,22 @@ daly_bms:
qr_code: qr_code:
- id: homepage_qr - id: homepage_qr
value: https://esphome.io/index.html value: https://esphome.io/index.html
alarm_control_panel:
- platform: template
id: alarmcontrolpanel1
name: Alarm Panel
codes:
- "1234"
requires_code_to_arm: true
arming_home_time: 1s
arming_away_time: 15s
pending_time: 15s
trigger_time: 30s
binary_sensors:
- input: bin1
bypass_armed_home: true
on_state:
then:
- lambda: !lambda |-
ESP_LOGD("TEST", "State change %s", alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()));