mirror of
https://github.com/esphome/esphome.git
synced 2025-01-18 12:05:41 +00:00
Merge branch 'dev' into jesserockz-2022-199
This commit is contained in:
commit
fcf0761ba3
@ -27,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
rev: v3.3.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
@ -110,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
@ -139,6 +140,7 @@ esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
esphome/components/max6956/* @looping40
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
@ -188,6 +190,7 @@ esphome/components/nfc/* @jesserockz
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pid/* @OttoWinter
|
||||
|
@ -26,7 +26,7 @@ RUN \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
python3-venv=3.9.2-3 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
git=1:2.30.2-1+deb11u2 \
|
||||
curl=7.74.0-1.3+deb11u7 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
@ -63,7 +63,7 @@ RUN \
|
||||
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
&& /platformio_install_deps.py /platformio.ini --libraries
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
|
@ -429,15 +429,16 @@ void APIServer::on_shutdown() {
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIServer::start_voice_assistant() {
|
||||
bool result = false;
|
||||
for (auto &c : this->clients_) {
|
||||
result |= c->request_voice_assistant(true);
|
||||
if (c->request_voice_assistant(true))
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
void APIServer::stop_voice_assistant() {
|
||||
for (auto &c : this->clients_) {
|
||||
c->request_voice_assistant(false);
|
||||
if (c->request_voice_assistant(false))
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -18,5 +18,5 @@ async def to_code(config):
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
|
||||
# https://github.com/esphome/ESPAsyncTCP
|
||||
cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3")
|
||||
|
@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_(
|
||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientPasskeyRequestTrigger = ble_client_ns.class_(
|
||||
"BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientPasskeyNotificationTrigger = ble_client_ns.class_(
|
||||
"BLEClientPasskeyNotificationTrigger",
|
||||
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
|
||||
)
|
||||
BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_(
|
||||
"BLEClientNumericComparisonRequestTrigger",
|
||||
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
|
||||
)
|
||||
|
||||
# Actions
|
||||
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
|
||||
BLEPasskeyReplyAction = ble_client_ns.class_(
|
||||
"BLEClientPasskeyReplyAction", automation.Action
|
||||
)
|
||||
BLENumericComparisonReplyAction = ble_client_ns.class_(
|
||||
"BLEClientNumericComparisonReplyAction", automation.Action
|
||||
)
|
||||
BLERemoveBondAction = ble_client_ns.class_(
|
||||
"BLEClientRemoveBondAction", automation.Action
|
||||
)
|
||||
|
||||
CONF_PASSKEY = "passkey"
|
||||
CONF_ACCEPT = "accept"
|
||||
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
|
||||
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
|
||||
|
||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
|
||||
# enforce this in yaml checks.
|
||||
@ -56,6 +83,29 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientPasskeyRequestTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientPasskeyNotificationTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientNumericComparisonRequestTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean),
|
||||
}
|
||||
)
|
||||
|
||||
BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
|
||||
)
|
||||
async def ble_write_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)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
value = config[CONF_VALUE]
|
||||
if cg.is_template(value):
|
||||
@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.numeric_comparison_reply",
|
||||
BLENumericComparisonReplyAction,
|
||||
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA,
|
||||
)
|
||||
async def numeric_comparison_reply_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
accept = config[CONF_ACCEPT]
|
||||
if cg.is_template(accept):
|
||||
templ = await cg.templatable(accept, args, cg.bool_)
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(accept))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA
|
||||
)
|
||||
async def passkey_reply_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
passkey = config[CONF_PASSKEY]
|
||||
if cg.is_template(passkey):
|
||||
templ = await cg.templatable(passkey, args, cg.uint32)
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(passkey))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.remove_bond",
|
||||
BLERemoveBondAction,
|
||||
BLE_REMOVE_BOND_ACTION_SCHEMA,
|
||||
)
|
||||
async def remove_bond_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@ -148,3 +267,12 @@ async def to_code(config):
|
||||
for conf in config.get(CONF_ON_DISCONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PASSKEY_REQUEST, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
|
||||
for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
|
||||
|
@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
uint32_t passkey = param->ble_security.key_notif.passkey;
|
||||
this->trigger(passkey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_NC_REQ_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
uint32_t passkey = param->ble_security.key_notif.passkey;
|
||||
this->trigger(passkey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEWriterClientNode : public BLEClientNode {
|
||||
public:
|
||||
BLEWriterClientNode(BLEClient *ble_client) {
|
||||
@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
uint32_t passkey;
|
||||
if (has_simple_value_) {
|
||||
passkey = this->value_simple_;
|
||||
} else {
|
||||
passkey = this->value_template_(x...);
|
||||
}
|
||||
if (passkey > 999999)
|
||||
return;
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
esp_ble_passkey_reply(remote_bda, true, passkey);
|
||||
}
|
||||
|
||||
void set_value_template(std::function<uint32_t(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const uint32_t &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
uint32_t value_simple_{0};
|
||||
std::function<uint32_t(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
if (has_simple_value_) {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_simple_);
|
||||
} else {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
|
||||
}
|
||||
}
|
||||
|
||||
void set_value_template(std::function<bool(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const bool &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
bool value_simple_{false};
|
||||
std::function<bool(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
esp_ble_remove_bond_device(remote_bda);
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
@ -27,7 +27,7 @@ class BLEClient;
|
||||
class BLEClientNode {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
esp_ble_gattc_cb_param_t *param){};
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
|
||||
virtual void loop() {}
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
|
@ -40,6 +40,7 @@ DEVICE = {
|
||||
|
||||
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
|
||||
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
|
||||
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
|
||||
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
|
||||
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
|
||||
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
|
||||
@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_mp3",
|
||||
PlayMp3Action,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FILE): cv.templatable(cv.int_),
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_FILE], args, float)
|
||||
cg.add(var.set_file(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play",
|
||||
PlayFileAction,
|
||||
|
@ -7,10 +7,10 @@ namespace dfplayer {
|
||||
static const char *const TAG = "dfplayer";
|
||||
|
||||
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
|
||||
if (folder < 100 && file < 256) {
|
||||
if (folder <= 10 && file <= 1000) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
|
||||
} else if (folder <= 10 && file <= 1000) {
|
||||
} else if (folder < 100 && file < 256) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
|
||||
} else {
|
||||
|
@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x02);
|
||||
}
|
||||
void play_mp3(uint16_t file) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x12, file);
|
||||
}
|
||||
void play_file(uint16_t file) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x03, file);
|
||||
@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
|
||||
|
||||
template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
this->parent_->play_mp3(file);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
@ -9,6 +9,7 @@ CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
|
||||
CONF_BLE_ID = "ble_id"
|
||||
CONF_IO_CAPABILITY = "io_capability"
|
||||
|
||||
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
@ -19,10 +20,21 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
|
||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
|
||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
|
||||
|
||||
IoCapability = esp32_ble_ns.enum("IoCapability")
|
||||
IO_CAPABILITY = {
|
||||
"none": IoCapability.IO_CAP_NONE,
|
||||
"keyboard_only": IoCapability.IO_CAP_IN,
|
||||
"keyboard_display": IoCapability.IO_CAP_KBDISP,
|
||||
"display_only": IoCapability.IO_CAP_OUT,
|
||||
"display_yes_no": IoCapability.IO_CAP_IO,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLE),
|
||||
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
|
||||
IO_CAPABILITY, lower=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
|
||||
return false;
|
||||
@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
void ESP32BLE::dump_config() {
|
||||
const uint8_t *mac_address = esp_bt_dev_get_address();
|
||||
if (mac_address) {
|
||||
const char *io_capability_s;
|
||||
switch (this->io_cap_) {
|
||||
case ESP_IO_CAP_OUT:
|
||||
io_capability_s = "display_only";
|
||||
break;
|
||||
case ESP_IO_CAP_IO:
|
||||
io_capability_s = "display_yes_no";
|
||||
break;
|
||||
case ESP_IO_CAP_IN:
|
||||
io_capability_s = "keyboard_only";
|
||||
break;
|
||||
case ESP_IO_CAP_NONE:
|
||||
io_capability_s = "none";
|
||||
break;
|
||||
case ESP_IO_CAP_KBDISP:
|
||||
io_capability_s = "keyboard_display";
|
||||
break;
|
||||
default:
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE:");
|
||||
ESP_LOGCONFIG(TAG, " MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2],
|
||||
mac_address[3], mac_address[4], mac_address[5]);
|
||||
ESP_LOGCONFIG(TAG, " IO Capability: %s", io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
|
||||
}
|
||||
|
@ -25,6 +25,14 @@ typedef struct {
|
||||
uint16_t mtu;
|
||||
} conn_status_t;
|
||||
|
||||
enum IoCapability {
|
||||
IO_CAP_OUT = ESP_IO_CAP_OUT,
|
||||
IO_CAP_IO = ESP_IO_CAP_IO,
|
||||
IO_CAP_IN = ESP_IO_CAP_IN,
|
||||
IO_CAP_NONE = ESP_IO_CAP_NONE,
|
||||
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
|
||||
};
|
||||
|
||||
class GAPEventHandler {
|
||||
public:
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
@ -44,6 +52,8 @@ class GATTsEventHandler {
|
||||
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
@ -72,6 +82,7 @@ class ESP32BLE : public Component {
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_;
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
|
||||
this->enrollment_done_callback_.call(this->enrollment_slot_);
|
||||
this->get_fingerprint_count_();
|
||||
} else {
|
||||
this->enrollment_failed_callback_.call(this->enrollment_slot_);
|
||||
if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
|
||||
this->enrollment_failed_callback_.call(this->enrollment_slot_);
|
||||
}
|
||||
}
|
||||
this->enrollment_image_ = 0;
|
||||
this->enrollment_slot_ = 0;
|
||||
this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
|
||||
if (this->enrolling_binary_sensor_ != nullptr) {
|
||||
this->enrolling_binary_sensor_->publish_state(false);
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ namespace fingerprint_grow {
|
||||
|
||||
static const uint16_t START_CODE = 0xEF01;
|
||||
|
||||
static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF;
|
||||
|
||||
enum GrowPacketType {
|
||||
COMMAND = 0x01,
|
||||
DATA = 0x02,
|
||||
@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||
uint32_t new_password_ = -1;
|
||||
GPIOPin *sensing_pin_{nullptr};
|
||||
uint8_t enrollment_image_ = 0;
|
||||
uint16_t enrollment_slot_ = 0;
|
||||
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
|
||||
uint8_t enrollment_buffers_ = 5;
|
||||
bool waiting_removal_ = false;
|
||||
uint32_t last_aura_led_control_ = 0;
|
||||
|
1
esphome/components/hyt271/__init__.py
Normal file
1
esphome/components/hyt271/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@Philippe12"]
|
52
esphome/components/hyt271/hyt271.cpp
Normal file
52
esphome/components/hyt271/hyt271.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "hyt271.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hyt271 {
|
||||
|
||||
static const char *const TAG = "hyt271";
|
||||
|
||||
static const uint8_t HYT271_ADDRESS = 0x28;
|
||||
|
||||
void HYT271Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HYT271:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
}
|
||||
void HYT271Component::update() {
|
||||
uint8_t raw_data[4];
|
||||
|
||||
if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values");
|
||||
return;
|
||||
}
|
||||
this->set_timeout("wait_convert", 50, [this]() {
|
||||
uint8_t raw_data[4];
|
||||
if (this->read(raw_data, 4) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values");
|
||||
return;
|
||||
}
|
||||
uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2;
|
||||
uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1];
|
||||
|
||||
float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f;
|
||||
float humidity = (float(raw_humidity)) * (100.0f / 16383.0f);
|
||||
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity);
|
||||
|
||||
if (this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(temperature);
|
||||
if (this->humidity_ != nullptr)
|
||||
this->humidity_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
float HYT271Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace hyt271
|
||||
} // namespace esphome
|
27
esphome/components/hyt271/hyt271.h
Normal file
27
esphome/components/hyt271/hyt271.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hyt271 {
|
||||
|
||||
class HYT271Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
|
||||
void dump_config() override;
|
||||
/// Update the sensor values (temperature+humidity).
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace hyt271
|
||||
} // namespace esphome
|
56
esphome/components/hyt271/sensor.py
Normal file
56
esphome/components/hyt271/sensor.py
Normal file
@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
hyt271_ns = cg.esphome_ns.namespace("hyt271")
|
||||
HYT271Component = hyt271_ns.class_(
|
||||
"HYT271Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HYT271Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x28))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
148
esphome/components/max6956/__init__.py
Normal file
148
esphome/components/max6956/__init__.py
Normal file
@ -0,0 +1,148 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins, automation
|
||||
from esphome.components import i2c
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NUMBER,
|
||||
CONF_MODE,
|
||||
CONF_INVERTED,
|
||||
CONF_INPUT,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@looping40"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_BRIGHTNESS_MODE = "brightness_mode"
|
||||
CONF_BRIGHTNESS_GLOBAL = "brightness_global"
|
||||
|
||||
|
||||
max6956_ns = cg.esphome_ns.namespace("max6956")
|
||||
|
||||
MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice)
|
||||
MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin)
|
||||
|
||||
# Actions
|
||||
SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action)
|
||||
SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action)
|
||||
|
||||
MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE")
|
||||
CURRENT_MODES = {
|
||||
"global": MAX6956_CURRENTMODE.GLOBAL,
|
||||
"segment": MAX6956_CURRENTMODE.SEGMENT,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(MAX6956),
|
||||
cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum(
|
||||
CURRENT_MODES, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE]))
|
||||
cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL]))
|
||||
|
||||
|
||||
def validate_mode(value):
|
||||
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_INPUT] and value[CONF_OUTPUT]:
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_PULLUP] and not value[CONF_INPUT]:
|
||||
raise cv.Invalid("Pullup only available with input")
|
||||
return value
|
||||
|
||||
|
||||
CONF_MAX6956 = "max6956"
|
||||
|
||||
MAX6956_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX6956GPIOPin),
|
||||
cv.Required(CONF_MAX6956): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31),
|
||||
cv.Optional(CONF_MODE, default={}): cv.All(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
},
|
||||
validate_mode,
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA)
|
||||
async def max6956_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_MAX6956])
|
||||
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6956.set_brightness_global",
|
||||
SetCurrentGlobalAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable(
|
||||
cv.int_range(min=0, max=15)
|
||||
),
|
||||
},
|
||||
key=CONF_BRIGHTNESS_GLOBAL,
|
||||
),
|
||||
)
|
||||
async def max6956_set_brightness_global_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)
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float)
|
||||
cg.add(var.set_brightness_global(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6956.set_brightness_mode",
|
||||
SetCurrentModeAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable(
|
||||
cv.enum(CURRENT_MODES, lower=True)
|
||||
),
|
||||
},
|
||||
key=CONF_BRIGHTNESS_MODE,
|
||||
),
|
||||
)
|
||||
async def max6956_set_brightness_mode_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)
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float)
|
||||
cg.add(var.set_brightness_mode(template_))
|
||||
return var
|
40
esphome/components/max6956/automation.h
Normal file
40
esphome/components/max6956/automation.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/max6956/max6956.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> {
|
||||
public:
|
||||
SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint8_t, brightness_global)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->max6956_->set_brightness_global(this->brightness_global_.value(x...));
|
||||
this->max6956_->write_brightness_global();
|
||||
}
|
||||
|
||||
protected:
|
||||
MAX6956 *max6956_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> {
|
||||
public:
|
||||
SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||
|
||||
TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...));
|
||||
this->max6956_->write_brightness_mode();
|
||||
}
|
||||
|
||||
protected:
|
||||
MAX6956 *max6956_;
|
||||
};
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
170
esphome/components/max6956/max6956.cpp
Normal file
170
esphome/components/max6956/max6956.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
#include "max6956.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
static const char *const TAG = "max6956";
|
||||
|
||||
/// Masks for MAX6956 Configuration register
|
||||
const uint32_t MASK_TRANSITION_DETECTION = 0x80;
|
||||
const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40;
|
||||
const uint32_t MASK_NORMAL_OPERATION = 0x01;
|
||||
|
||||
const uint32_t MASK_1PORT_VALUE = 0x03;
|
||||
const uint32_t MASK_PORT_CONFIG = 0x03;
|
||||
const uint8_t MASK_CONFIG_CURRENT = 0x40;
|
||||
const uint8_t MASK_CURRENT_PIN = 0x0F;
|
||||
|
||||
/**************************************
|
||||
* MAX6956 *
|
||||
**************************************/
|
||||
void MAX6956::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX6956...");
|
||||
uint8_t configuration;
|
||||
if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
write_brightness_global();
|
||||
write_brightness_mode();
|
||||
|
||||
/** TO DO : read transition detection in yaml
|
||||
TO DO : read indivdual current in yaml **/
|
||||
this->read_reg_(MAX6956_CONFIGURATION, &configuration);
|
||||
ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||
configuration = configuration | MASK_NORMAL_OPERATION;
|
||||
this->write_reg_(MAX6956_CONFIGURATION, configuration);
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Enabling normal operation");
|
||||
ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||
}
|
||||
|
||||
bool MAX6956::digital_read(uint8_t pin) {
|
||||
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||
uint8_t value = 0;
|
||||
this->read_reg_(reg_addr, &value);
|
||||
return (value & MASK_1PORT_VALUE);
|
||||
}
|
||||
|
||||
void MAX6956::digital_write(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||
this->write_reg_(reg_addr, value);
|
||||
}
|
||||
|
||||
void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 2 * (pin % 4);
|
||||
MAX6956GPIOMode mode = MAX6956_INPUT;
|
||||
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
mode = MAX6956GPIOMode::MAX6956_INPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
mode = MAX6956GPIOMode::MAX6956_OUTPUT;
|
||||
}
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_PORT_CONFIG << shift);
|
||||
config |= (mode << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) {
|
||||
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 2 * (pin % 4);
|
||||
MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED;
|
||||
|
||||
if (flags == max6956::FLAG_LED) {
|
||||
mode = MAX6956GPIOMode::MAX6956_LED;
|
||||
}
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_PORT_CONFIG << shift);
|
||||
config |= (mode << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::set_brightness_global(uint8_t current) {
|
||||
if (current > 15) {
|
||||
ESP_LOGE(TAG, "Global brightness out off range (%u)", current);
|
||||
return;
|
||||
}
|
||||
global_brightness_ = current;
|
||||
}
|
||||
|
||||
void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); }
|
||||
|
||||
void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; };
|
||||
|
||||
void MAX6956::write_brightness_mode() {
|
||||
uint8_t reg_addr = MAX6956_CONFIGURATION;
|
||||
uint8_t config = 0;
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~MASK_CONFIG_CURRENT;
|
||||
config |= brightness_mode_ << 6;
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
|
||||
uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 4 * (pin % 2);
|
||||
uint8_t bright = roundf(brightness * 15);
|
||||
|
||||
if (prev_bright_[pin - MAX6956_MIN] == bright)
|
||||
return;
|
||||
|
||||
prev_bright_[pin - MAX6956_MIN] = bright;
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_CURRENT_PIN << shift);
|
||||
config |= (bright << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
return this->read_byte(reg, value);
|
||||
}
|
||||
|
||||
bool MAX6956::write_reg_(uint8_t reg, uint8_t value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
return this->write_byte(reg, value);
|
||||
}
|
||||
|
||||
void MAX6956::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX6956");
|
||||
|
||||
if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) {
|
||||
ESP_LOGCONFIG(TAG, "current mode: global");
|
||||
ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "current mode: segment");
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* MAX6956GPIOPin *
|
||||
**************************************/
|
||||
void MAX6956GPIOPin::setup() { pin_mode(flags_); }
|
||||
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
std::string MAX6956GPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
94
esphome/components/max6956/max6956.h
Normal file
94
esphome/components/max6956/max6956.h
Normal file
@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
/// Modes for MAX6956 pins
|
||||
enum MAX6956GPIOMode : uint8_t {
|
||||
MAX6956_LED = 0x00,
|
||||
MAX6956_OUTPUT = 0x01,
|
||||
MAX6956_INPUT = 0x02,
|
||||
MAX6956_INPUT_PULLUP = 0x03
|
||||
};
|
||||
|
||||
/// Range for MAX6956 pins
|
||||
enum MAX6956GPIORange : uint8_t {
|
||||
MAX6956_MIN = 4,
|
||||
MAX6956_MAX = 31,
|
||||
};
|
||||
|
||||
enum MAX6956GPIORegisters {
|
||||
MAX6956_GLOBAL_CURRENT = 0x02,
|
||||
MAX6956_CONFIGURATION = 0x04,
|
||||
MAX6956_TRANSITION_DETECT_MASK = 0x06,
|
||||
MAX6956_DISPLAY_TEST = 0x07,
|
||||
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
|
||||
MAX6956_CURRENT_START = 0x12, // Current054
|
||||
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
|
||||
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4–11 (data bits D0–D7)
|
||||
};
|
||||
|
||||
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
|
||||
|
||||
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
|
||||
|
||||
class MAX6956 : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
MAX6956() = default;
|
||||
|
||||
void setup() override;
|
||||
|
||||
bool digital_read(uint8_t pin);
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void set_brightness_global(uint8_t current);
|
||||
void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode);
|
||||
void set_pin_brightness(uint8_t pin, float brightness);
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void write_brightness_global();
|
||||
void write_brightness_mode();
|
||||
|
||||
protected:
|
||||
// read a given register
|
||||
bool read_reg_(uint8_t reg, uint8_t *value);
|
||||
// write a value to a given register
|
||||
bool write_reg_(uint8_t reg, uint8_t value);
|
||||
max6956::MAX6956CURRENTMODE brightness_mode_;
|
||||
uint8_t global_brightness_;
|
||||
|
||||
private:
|
||||
int8_t prev_bright_[28] = {0};
|
||||
};
|
||||
|
||||
class MAX6956GPIOPin : public GPIOPin {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
|
||||
void set_parent(MAX6956 *parent) { parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
protected:
|
||||
MAX6956 *parent_;
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
28
esphome/components/max6956/output/__init__.py
Normal file
28
esphome/components/max6956/output/__init__.py
Normal file
@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_PIN, CONF_ID
|
||||
from .. import MAX6956, max6956_ns, CONF_MAX6956
|
||||
|
||||
DEPENDENCIES = ["max6956"]
|
||||
|
||||
MAX6956LedChannel = max6956_ns.class_(
|
||||
"MAX6956LedChannel", output.FloatOutput, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel),
|
||||
cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_PIN): cv.int_range(min=4, max=31),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_MAX6956])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
cg.add(var.set_parent(parent))
|
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "max6956_led_output.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
static const char *const TAG = "max6956_led_channel";
|
||||
|
||||
void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); }
|
||||
|
||||
void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); }
|
||||
|
||||
void MAX6956LedChannel::setup() {
|
||||
this->parent_->pin_mode(this->pin_, max6956::FLAG_LED);
|
||||
this->turn_off();
|
||||
}
|
||||
|
||||
void MAX6956LedChannel::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX6956 current:");
|
||||
ESP_LOGCONFIG(TAG, " MAX6956 pin: %d", this->pin_);
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
}
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/max6956/max6956.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
class MAX6956;
|
||||
|
||||
class MAX6956LedChannel : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void set_parent(MAX6956 *parent) { this->parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
void write_state(bool state) override;
|
||||
|
||||
MAX6956 *parent_;
|
||||
uint8_t pin_;
|
||||
};
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
78
esphome/components/pca6416a/__init__.py
Normal file
78
esphome/components/pca6416a/__init__.py
Normal file
@ -0,0 +1,78 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import i2c
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
CONF_MODE,
|
||||
CONF_INVERTED,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Mat931"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
|
||||
|
||||
PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice)
|
||||
PCA6416AGPIOPin = pca6416a_ns.class_(
|
||||
"PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent)
|
||||
)
|
||||
|
||||
CONF_PCA6416A = "pca6416a"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x21))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
|
||||
def validate_mode(value):
|
||||
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_INPUT] and value[CONF_OUTPUT]:
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_PULLUP] and not value[CONF_INPUT]:
|
||||
raise cv.Invalid("Pullup only available with input")
|
||||
return value
|
||||
|
||||
|
||||
PCA6416A_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin),
|
||||
cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16),
|
||||
cv.Optional(CONF_MODE, default={}): cv.All(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
},
|
||||
validate_mode,
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA)
|
||||
async def pca6416a_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_PCA6416A])
|
||||
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include "pca6416a.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca6416a {
|
||||
|
||||
enum PCA6416AGPIORegisters {
|
||||
// 0 side
|
||||
PCA6416A_INPUT0 = 0x00,
|
||||
PCA6416A_OUTPUT0 = 0x02,
|
||||
PCA6416A_INVERT0 = 0x04,
|
||||
PCA6416A_CONFIG0 = 0x06,
|
||||
PCAL6416A_PULL_EN0 = 0x46,
|
||||
PCAL6416A_PULL_DIR0 = 0x48,
|
||||
// 1 side
|
||||
PCA6416A_INPUT1 = 0x01,
|
||||
PCA6416A_OUTPUT1 = 0x03,
|
||||
PCA6416A_INVERT1 = 0x05,
|
||||
PCA6416A_CONFIG1 = 0x07,
|
||||
PCAL6416A_PULL_EN1 = 0x47,
|
||||
PCAL6416A_PULL_DIR1 = 0x49,
|
||||
};
|
||||
|
||||
static const char *const TAG = "pca6416a";
|
||||
|
||||
void PCA6416AComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PCA6416A...");
|
||||
// Test to see if device exists
|
||||
uint8_t value;
|
||||
if (!this->read_register_(PCA6416A_INPUT0, &value)) {
|
||||
ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Test to see if the device supports pull-up resistors
|
||||
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) {
|
||||
this->has_pullup_ = true;
|
||||
}
|
||||
|
||||
// No polarity inversion
|
||||
this->write_register_(PCA6416A_INVERT0, 0);
|
||||
this->write_register_(PCA6416A_INVERT1, 0);
|
||||
// Set all pins to input
|
||||
this->write_register_(PCA6416A_CONFIG0, 0xff);
|
||||
this->write_register_(PCA6416A_CONFIG1, 0xff);
|
||||
// Read current output register state
|
||||
this->read_register_(PCA6416A_OUTPUT0, &this->output_0_);
|
||||
this->read_register_(PCA6416A_OUTPUT1, &this->output_1_);
|
||||
|
||||
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||
this->status_has_error());
|
||||
}
|
||||
|
||||
void PCA6416AComponent::dump_config() {
|
||||
if (this->has_pullup_) {
|
||||
ESP_LOGCONFIG(TAG, "PCAL6416A:");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "PCA6416A:");
|
||||
}
|
||||
LOG_I2C_DEVICE(this)
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with PCA6416A failed!");
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::digital_read(uint8_t pin) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
|
||||
uint8_t value = 0;
|
||||
this->read_register_(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
}
|
||||
|
||||
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
|
||||
this->update_register_(pin, value, reg_addr);
|
||||
}
|
||||
|
||||
void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1;
|
||||
uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1;
|
||||
uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
this->update_register_(pin, true, io_dir);
|
||||
if (has_pullup_) {
|
||||
this->update_register_(pin, true, pull_dir);
|
||||
this->update_register_(pin, false, pull_en);
|
||||
}
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
this->update_register_(pin, true, io_dir);
|
||||
if (has_pullup_) {
|
||||
this->update_register_(pin, true, pull_dir);
|
||||
this->update_register_(pin, true, pull_en);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
|
||||
}
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
this->update_register_(pin, false, io_dir);
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGD(TAG, "Device marked failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGD(TAG, "Device marked failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_value = 0;
|
||||
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||
reg_value = this->output_0_;
|
||||
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||
reg_value = this->output_1_;
|
||||
} else {
|
||||
this->read_register_(reg_addr, ®_value);
|
||||
}
|
||||
|
||||
if (pin_value) {
|
||||
reg_value |= 1 << bit;
|
||||
} else {
|
||||
reg_value &= ~(1 << bit);
|
||||
}
|
||||
|
||||
this->write_register_(reg_addr, reg_value);
|
||||
|
||||
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||
this->output_0_ = reg_value;
|
||||
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||
this->output_1_ = reg_value;
|
||||
}
|
||||
}
|
||||
|
||||
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
void PCA6416AGPIOPin::setup() { pin_mode(flags_); }
|
||||
void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
std::string PCA6416AGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace pca6416a
|
||||
} // namespace esphome
|
63
esphome/components/pca6416a/pca6416a.h
Normal file
63
esphome/components/pca6416a/pca6416a.h
Normal file
@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca6416a {
|
||||
|
||||
class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
PCA6416AComponent() = default;
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
/// Helper function to set the pin mode of a pin.
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool read_register_(uint8_t reg, uint8_t *value);
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
|
||||
|
||||
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||
uint8_t output_0_{0x00};
|
||||
uint8_t output_1_{0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
/// Only the PCAL6416A has pull-up resistors
|
||||
bool has_pullup_{false};
|
||||
};
|
||||
|
||||
/// Helper class to expose a PCA6416A pin as an internal input GPIO pin.
|
||||
class PCA6416AGPIOPin : public GPIOPin {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
|
||||
void set_parent(PCA6416AComponent *parent) { parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
protected:
|
||||
PCA6416AComponent *parent_;
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace pca6416a
|
||||
} // namespace esphome
|
@ -81,7 +81,32 @@ void PN532::setup() {
|
||||
this->turn_off_rf_();
|
||||
}
|
||||
|
||||
bool PN532::powerdown() {
|
||||
updates_enabled_ = false;
|
||||
requested_read_ = false;
|
||||
ESP_LOGI(TAG, "Powering down PN532");
|
||||
if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup
|
||||
ESP_LOGE(TAG, "Error writing powerdown command to PN532");
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
|
||||
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
|
||||
return false;
|
||||
}
|
||||
if (response[0] != 0x00) {
|
||||
ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "Powerdown successful");
|
||||
delay(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PN532::update() {
|
||||
if (!updates_enabled_)
|
||||
return;
|
||||
|
||||
for (auto *obj : this->binary_sensors_)
|
||||
obj->on_scan_end();
|
||||
|
||||
|
@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
|
||||
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
|
||||
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
||||
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
|
||||
|
||||
class PN532BinarySensor;
|
||||
|
||||
@ -30,6 +31,7 @@ class PN532 : public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void loop() override;
|
||||
void on_shutdown() override { powerdown(); }
|
||||
|
||||
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
@ -45,6 +47,7 @@ class PN532 : public PollingComponent {
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode(nfc::NdefMessage *message);
|
||||
bool powerdown();
|
||||
|
||||
protected:
|
||||
void turn_off_rf_();
|
||||
@ -79,6 +82,7 @@ class PN532 : public PollingComponent {
|
||||
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
||||
bool clean_mifare_ultralight_();
|
||||
|
||||
bool updates_enabled_{true};
|
||||
bool requested_read_{false};
|
||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
|
@ -791,6 +791,57 @@ async def raw_action(var, config, args):
|
||||
cg.add(var.set_carrier_frequency(templ))
|
||||
|
||||
|
||||
# Drayton
|
||||
(
|
||||
DraytonData,
|
||||
DraytonBinarySensor,
|
||||
DraytonTrigger,
|
||||
DraytonAction,
|
||||
DraytonDumper,
|
||||
) = declare_protocol("Drayton")
|
||||
DRAYTON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
|
||||
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
|
||||
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
|
||||
def drayton_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
DraytonData,
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("channel", config[CONF_CHANNEL]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("drayton", DraytonTrigger, DraytonData)
|
||||
def drayton_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("drayton", DraytonDumper)
|
||||
def drayton_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
|
||||
async def drayton_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
|
||||
cg.add(var.set_address(template_))
|
||||
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
|
||||
# RC5
|
||||
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
||||
RC5_SCHEMA = cv.Schema(
|
||||
|
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include "drayton_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.drayton";
|
||||
|
||||
static const uint32_t BIT_TIME_US = 500;
|
||||
static const uint8_t CARRIER_KHZ = 2;
|
||||
static const uint8_t NBITS_PREAMBLE = 12;
|
||||
static const uint8_t NBITS_SYNC = 4;
|
||||
static const uint8_t NBITS_ADDRESS = 16;
|
||||
static const uint8_t NBITS_CHANNEL = 5;
|
||||
static const uint8_t NBITS_COMMAND = 7;
|
||||
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
||||
|
||||
static const uint8_t CMD_ON = 0x41;
|
||||
static const uint8_t CMD_OFF = 0x02;
|
||||
|
||||
/*
|
||||
Drayton Protocol
|
||||
Using an oscilloscope to capture the data transmitted by the Digistat two
|
||||
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
|
||||
has a period of 500us, a bit rate of 2000 baud.
|
||||
|
||||
Each packet consists of an initial 1010 pattern to set up the receiver bias.
|
||||
The number of these bits seen at the receiver varies depending on the state
|
||||
of the bias when the packet transmission starts. The receiver algoritmn takes
|
||||
account of this.
|
||||
|
||||
The packet appears to be Manchester encoded, with a '10' tranmitted pair
|
||||
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
|
||||
begun with a '1100' syncronisation symbol which breaks this rule. Following
|
||||
the sync are 28 '01' or '10' pairs.
|
||||
|
||||
--------------------
|
||||
|
||||
Boiler On Command as received:
|
||||
101010101010110001101001010101101001010101010101100101010101101001011001
|
||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
|
||||
|
||||
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
|
||||
|
||||
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
|
||||
|
||||
Boiler Off Command as received:
|
||||
101010101010110001101001010101101001010101010101010101010110011001011001
|
||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
|
||||
|
||||
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
|
||||
|
||||
--------------------
|
||||
|
||||
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
|
||||
capture and retransmit the Digistat packets. RFLink splits each packet into an
|
||||
ID, SWITCH, and CMD field.
|
||||
|
||||
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
|
||||
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
|
||||
|
||||
--------------------
|
||||
|
||||
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
|
||||
channel and Command values of:
|
||||
|
||||
On 6180832 0110000110000000 1000001 10010
|
||||
address: '0x6180' channel: '0x12' command: '0x41'
|
||||
|
||||
Off 6180052 0110000110000000 0000010 10010
|
||||
address: '0x6180' channel: '0x12' command: '0x02'
|
||||
|
||||
These values are slightly different to those used by RFLink (the RFLink
|
||||
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
|
||||
is correct. A larger data sample would help (I have only found five different
|
||||
packet captures online) or definitive information from Drayton.
|
||||
|
||||
Splitting each packet in this way works well for me with esphome. Any
|
||||
corrections or additional data samples would be gratefully received.
|
||||
|
||||
marshn
|
||||
|
||||
*/
|
||||
|
||||
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||
uint16_t khz = CARRIER_KHZ;
|
||||
dst->set_carrier_frequency(khz * 1000);
|
||||
|
||||
// Preamble = 101010101010
|
||||
uint32_t out_data = 0x0AAA;
|
||||
for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync = 1100
|
||||
out_data = 0x000C;
|
||||
for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
|
||||
|
||||
out_data = data.address;
|
||||
out_data <<= NBITS_COMMAND;
|
||||
out_data |= data.command;
|
||||
out_data <<= NBITS_CHANNEL;
|
||||
out_data |= data.channel;
|
||||
|
||||
ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
|
||||
|
||||
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
dst->space(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
dst->mark(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
DraytonData out{
|
||||
.address = 0,
|
||||
.channel = 0,
|
||||
.command = 0,
|
||||
};
|
||||
|
||||
if (src.size() < 45) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
|
||||
src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
|
||||
src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
||||
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||
|
||||
// If first preamble item is a space, skip it
|
||||
if (src.peek_space_at_least(1)) {
|
||||
src.advance(1);
|
||||
}
|
||||
|
||||
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||
for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
|
||||
if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
|
||||
(src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
|
||||
src.advance(preamble + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read data. Index points to space of sync symbol
|
||||
// Extract first bit
|
||||
// Checks next bit to leave index pointing correctly
|
||||
uint32_t out_data = 0;
|
||||
uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
|
||||
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||
out_data |= 0 << bit;
|
||||
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
||||
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
|
||||
return {};
|
||||
}
|
||||
|
||||
// Before/after each bit is read the index points to the transition at the start of the bit period or,
|
||||
// if there is no transition at the start of the bit period, then the transition in the middle of
|
||||
// the previous bit period.
|
||||
while (--bit >= 1) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||
if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
|
||||
(src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||
out_data |= 0 << bit;
|
||||
} else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
|
||||
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
|
||||
out_data |= 0;
|
||||
} else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
|
||||
out_data |= 1;
|
||||
}
|
||||
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||
|
||||
out.channel = (uint8_t) (out_data & 0x1F);
|
||||
out_data >>= NBITS_CHANNEL;
|
||||
out.command = (uint8_t) (out_data & 0x7F);
|
||||
out_data >>= NBITS_COMMAND;
|
||||
out.address = (uint16_t) (out_data & 0xFFFF);
|
||||
|
||||
return out;
|
||||
}
|
||||
void DraytonProtocol::dump(const DraytonData &data) {
|
||||
ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
|
||||
((data.address << 1) & 0xffff), data.channel, data.command);
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
44
esphome/components/remote_base/drayton_protocol.h
Normal file
44
esphome/components/remote_base/drayton_protocol.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
struct DraytonData {
|
||||
uint16_t address;
|
||||
uint8_t channel;
|
||||
uint8_t command;
|
||||
|
||||
bool operator==(const DraytonData &rhs) const {
|
||||
return address == rhs.address && channel == rhs.channel && command == rhs.command;
|
||||
}
|
||||
};
|
||||
|
||||
class DraytonProtocol : public RemoteProtocol<DraytonData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
|
||||
optional<DraytonData> decode(RemoteReceiveData src) override;
|
||||
void dump(const DraytonData &data) override;
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Drayton)
|
||||
|
||||
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, address)
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
TEMPLATABLE_VALUE(uint8_t, command)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
DraytonData data{};
|
||||
data.address = this->address_.value(x...);
|
||||
data.channel = this->channel_.value(x...);
|
||||
data.command = this->command_.value(x...);
|
||||
DraytonProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
|
@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Sprinkler::any_controller_is_active() {
|
||||
if (this->state_ != IDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &controller : this->other_controllers_) {
|
||||
if (controller != this) { // dummy check
|
||||
if (controller->controller_state() != IDLE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].controller_switch;
|
||||
|
@ -406,6 +406,12 @@ class Sprinkler : public Component {
|
||||
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
|
||||
optional<uint32_t> time_remaining_current_operation();
|
||||
|
||||
/// returns true if this or any sprinkler controller this controller knows about is active
|
||||
bool any_controller_is_active();
|
||||
|
||||
/// returns the current state of the sprinkler controller
|
||||
SprinklerState controller_state() { return this->state_; };
|
||||
|
||||
/// returns a pointer to a valve's control switch object
|
||||
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
||||
|
||||
@ -503,7 +509,6 @@ class Sprinkler : public Component {
|
||||
/// callback functions for timers
|
||||
void valve_selection_callback_();
|
||||
void sm_timer_callback_();
|
||||
void pump_stop_delay_callback_();
|
||||
|
||||
/// Maximum allowed queue size
|
||||
const uint8_t max_queue_size_{100};
|
||||
|
@ -39,6 +39,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_(
|
||||
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper5P8In", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper5P8InV2", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper7P5In = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper7P5In", WaveshareEPaper
|
||||
)
|
||||
@ -80,6 +83,7 @@ MODELS = {
|
||||
"4.20in": ("b", WaveshareEPaper4P2In),
|
||||
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
||||
"5.83in": ("b", WaveshareEPaper5P8In),
|
||||
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
||||
"7.50in": ("b", WaveshareEPaper7P5In),
|
||||
"7.50in-bv2": ("b", WaveshareEPaper7P5InBV2),
|
||||
"7.50in-bc": ("b", WaveshareEPaper7P5InBC),
|
||||
|
@ -1037,6 +1037,88 @@ void WaveshareEPaper5P8In::dump_config() {
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 5.83in V2
|
||||
// Datasheet/Specification/Reference:
|
||||
// - https://www.waveshare.com/w/upload/3/37/5.83inch_e-Paper_V2_Specification.pdf
|
||||
// - https://github.com/waveshare/e-Paper/blob/master/Arduino/epd5in83_V2/epd5in83_V2.cpp
|
||||
// ========================================================
|
||||
void WaveshareEPaper5P8InV2::initialize() {
|
||||
// COMMAND POWER SETTING
|
||||
this->command(0x01);
|
||||
this->data(0x07);
|
||||
this->data(0x07);
|
||||
this->data(0x3f);
|
||||
this->data(0x3f);
|
||||
|
||||
// COMMAND POWER ON
|
||||
this->command(0x04);
|
||||
delay(10);
|
||||
this->wait_until_idle_();
|
||||
|
||||
// PANNEL SETTING
|
||||
this->command(0x00);
|
||||
this->data(0x1F);
|
||||
|
||||
// COMMAND RESOLUTION SETTING
|
||||
this->command(0x61);
|
||||
this->data(0x02);
|
||||
this->data(0x88);
|
||||
this->data(0x01);
|
||||
this->data(0xE0);
|
||||
|
||||
this->command(0x15);
|
||||
this->data(0x00);
|
||||
|
||||
// COMMAND TCON SETTING
|
||||
this->command(0x60);
|
||||
this->data(0x22);
|
||||
|
||||
// Do we need this?
|
||||
// COMMAND PLL CONTROL
|
||||
this->command(0x30);
|
||||
this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
|
||||
}
|
||||
void HOT WaveshareEPaper5P8InV2::display() {
|
||||
// Reuse the code from WaveshareEPaper4P2In::display()
|
||||
// COMMAND VCM DC SETTING REGISTER
|
||||
this->command(0x82);
|
||||
this->data(0x12);
|
||||
|
||||
// COMMAND VCOM AND DATA INTERVAL SETTING
|
||||
this->command(0x50);
|
||||
this->data(0x97);
|
||||
|
||||
// COMMAND DATA START TRANSMISSION 1
|
||||
this->command(0x10);
|
||||
delay(2);
|
||||
this->start_data_();
|
||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||
this->end_data_();
|
||||
delay(2);
|
||||
|
||||
// COMMAND DATA START TRANSMISSION 2
|
||||
this->command(0x13);
|
||||
delay(2);
|
||||
this->start_data_();
|
||||
this->write_array(this->buffer_, this->get_buffer_length_());
|
||||
this->end_data_();
|
||||
|
||||
// COMMAND DISPLAY REFRESH
|
||||
this->command(0x12);
|
||||
}
|
||||
int WaveshareEPaper5P8InV2::get_width_internal() { return 648; }
|
||||
int WaveshareEPaper5P8InV2::get_height_internal() { return 480; }
|
||||
void WaveshareEPaper5P8InV2::dump_config() {
|
||||
LOG_DISPLAY("", "Waveshare E-Paper", this);
|
||||
ESP_LOGCONFIG(TAG, " Model: 5.83inv2");
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void WaveshareEPaper7P5InBV2::initialize() {
|
||||
// COMMAND POWER SETTING
|
||||
this->command(0x01);
|
||||
|
@ -284,6 +284,49 @@ class WaveshareEPaper5P8In : public WaveshareEPaper {
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaper5P8InV2 : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
||||
void display() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void deep_sleep() override {
|
||||
// COMMAND VCOM AND DATA INTERVAL SETTING
|
||||
this->command(0x50);
|
||||
this->data(0x17); // border floating
|
||||
|
||||
// COMMAND VCM DC SETTING
|
||||
this->command(0x82);
|
||||
// COMMAND PANEL SETTING
|
||||
this->command(0x00);
|
||||
|
||||
delay(100); // NOLINT
|
||||
|
||||
// COMMAND POWER SETTING
|
||||
this->command(0x01);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
this->data(0x00);
|
||||
delay(100); // NOLINT
|
||||
|
||||
// COMMAND POWER OFF
|
||||
this->command(0x02);
|
||||
this->wait_until_idle_();
|
||||
// COMMAND DEEP SLEEP
|
||||
this->command(0x07);
|
||||
this->data(0xA5); // check byte
|
||||
}
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
|
||||
int get_height_internal() override;
|
||||
};
|
||||
|
||||
class WaveshareEPaper7P5In : public WaveshareEPaper {
|
||||
public:
|
||||
void initialize() override;
|
||||
|
@ -428,6 +428,9 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["assumed_state"] = obj->assumed_state();
|
||||
}
|
||||
});
|
||||
}
|
||||
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
|
@ -88,7 +88,7 @@ lib_deps =
|
||||
${common:arduino.lib_deps}
|
||||
ESP8266WiFi ; wifi (Arduino built-in)
|
||||
Update ; ota (Arduino built-in)
|
||||
ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp
|
||||
esphome/ESPAsyncTCP-esphome@1.2.3 ; async_tcp
|
||||
ESP8266HTTPClient ; http_request (Arduino built-in)
|
||||
ESP8266mDNS ; mdns (Arduino built-in)
|
||||
DNSServer ; captive_portal (Arduino built-in)
|
||||
|
@ -10,8 +10,8 @@ platformio==6.1.6 # When updating platformio, also update Dockerfile
|
||||
esptool==4.5.1
|
||||
click==8.1.3
|
||||
esphome-dashboard==20230214.0
|
||||
aioesphomeapi==13.7.1
|
||||
zeroconf==0.56.0
|
||||
aioesphomeapi==13.7.2
|
||||
zeroconf==0.60.0
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
|
||||
|
@ -1,7 +1,7 @@
|
||||
pylint==2.17.3
|
||||
flake8==6.0.0 # also change in .pre-commit-config.yaml when updating
|
||||
black==23.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.3.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
|
@ -2,12 +2,22 @@
|
||||
# This script is used to preinstall
|
||||
# all platformio libraries in the global storage
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
config = configparser.ConfigParser(inline_comment_prefixes=(";",))
|
||||
config.read(sys.argv[1])
|
||||
|
||||
parser = argparse.ArgumentParser(description="")
|
||||
parser.add_argument("file", help="Path to platformio.ini", nargs=1)
|
||||
parser.add_argument("-l", "--libraries", help="Install libraries", action="store_true")
|
||||
parser.add_argument("-p", "--platforms", help="Install platforms", action="store_true")
|
||||
parser.add_argument("-t", "--tools", help="Install tools", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
config.read(args.file)
|
||||
|
||||
|
||||
libs = []
|
||||
tools = []
|
||||
@ -15,7 +25,7 @@ platforms = []
|
||||
# Extract from every lib_deps key in all sections
|
||||
for section in config.sections():
|
||||
conf = config[section]
|
||||
if "lib_deps" in conf:
|
||||
if "lib_deps" in conf and args.libraries:
|
||||
for lib_dep in conf["lib_deps"].splitlines():
|
||||
if not lib_dep:
|
||||
# Empty line or comment
|
||||
@ -28,10 +38,10 @@ for section in config.sections():
|
||||
continue
|
||||
libs.append("-l")
|
||||
libs.append(lib_dep)
|
||||
if "platform" in conf:
|
||||
if "platform" in conf and args.platforms:
|
||||
platforms.append("-p")
|
||||
platforms.append(conf["platform"])
|
||||
if "platform_packages" in conf:
|
||||
if "platform_packages" in conf and args.tools:
|
||||
for tool in conf["platform_packages"].splitlines():
|
||||
if not tool:
|
||||
# Empty line or comment
|
||||
|
@ -15,4 +15,4 @@ pip3 install --no-use-pep517 -e .
|
||||
|
||||
pre-commit install
|
||||
|
||||
script/platformio_install_deps.py platformio.ini
|
||||
script/platformio_install_deps.py platformio.ini --libraries --tools --platforms
|
||||
|
@ -294,6 +294,9 @@ wled:
|
||||
|
||||
adalight:
|
||||
|
||||
esp32_ble:
|
||||
io_capability: keyboard_only
|
||||
|
||||
esp32_ble_tracker:
|
||||
|
||||
ble_client:
|
||||
@ -307,6 +310,19 @@ ble_client:
|
||||
on_disconnect:
|
||||
then:
|
||||
- switch.turn_on: ble1_status
|
||||
on_passkey_request:
|
||||
then:
|
||||
- ble_client.passkey_reply:
|
||||
id: ble_blah
|
||||
passkey: 123456
|
||||
on_passkey_notification:
|
||||
then:
|
||||
- logger.log: "Passkey notification received"
|
||||
on_numeric_comparison_request:
|
||||
then:
|
||||
- ble_client.numeric_comparison_reply:
|
||||
id: ble_blah
|
||||
accept: True
|
||||
- mac_address: C4:4F:33:11:22:33
|
||||
id: my_bedjet_ble_client
|
||||
bedjet:
|
||||
@ -1276,6 +1292,13 @@ sensor:
|
||||
name: DHT Absolute Humidity
|
||||
temperature: dht_temperature
|
||||
humidity: dht_humidity
|
||||
- platform: hyt271
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: "Temperature hyt271"
|
||||
id: temp_etuve
|
||||
humidity:
|
||||
name: "Humidity hyt271"
|
||||
|
||||
esp32_touch:
|
||||
setup_mode: false
|
||||
@ -1449,6 +1472,13 @@ binary_sensor:
|
||||
number: 1
|
||||
mode: INPUT
|
||||
inverted: true
|
||||
- platform: gpio
|
||||
name: PCA6416A binary sensor
|
||||
pin:
|
||||
pca6416a: pca6416a_hub
|
||||
number: 15
|
||||
mode: INPUT
|
||||
inverted: true
|
||||
- platform: gpio
|
||||
name: MCP21 binary sensor
|
||||
pin:
|
||||
@ -2934,6 +2964,11 @@ pca9554:
|
||||
address: 0x3F
|
||||
i2c_id: i2c_bus
|
||||
|
||||
pca6416a:
|
||||
- id: pca6416a_hub
|
||||
address: 0x21
|
||||
i2c_id: i2c_bus
|
||||
|
||||
mcp23017:
|
||||
- id: mcp23017_hub
|
||||
open_drain_interrupt: true
|
||||
|
@ -374,6 +374,16 @@ binary_sensor:
|
||||
on_press:
|
||||
- logger.log: Touched
|
||||
|
||||
- platform: gpio
|
||||
name: MaxIn Pin 4
|
||||
pin:
|
||||
max6956: max6956_1
|
||||
number: 4
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
inverted: false
|
||||
|
||||
climate:
|
||||
- platform: tuya
|
||||
id: tuya_climate
|
||||
@ -717,4 +727,8 @@ voice_assistant:
|
||||
format: "Voice assistant error - code %s, message: %s"
|
||||
args: [code.c_str(), message.c_str()]
|
||||
|
||||
max6956:
|
||||
- id: max6956_1
|
||||
address: 0x40
|
||||
|
||||
es8388:
|
||||
|
Loading…
x
Reference in New Issue
Block a user