Compare commits
6 Commits
release
...
bump-docke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c49c41ee2f | ||
|
|
8998c5f6dd | ||
|
|
3a9ab50dd2 | ||
|
|
5abd91d6d5 | ||
|
|
c3da42516b | ||
|
|
ec1fae6883 |
@@ -6,13 +6,13 @@
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:6.0.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:6.0.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:6.0.0 AS base-hassio-armv7
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-armv7
|
||||
FROM debian:bullseye-20220527-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220527-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220527-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
|
||||
@@ -76,8 +76,6 @@ async def to_code(config):
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
frame = image.convert("RGB")
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
|
||||
@@ -72,8 +72,6 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod
|
||||
// We could fetch biodata from bedjet and set these names that way.
|
||||
// But then we have to invert the lookup in order to send the right preset.
|
||||
// For now, we can leave them as M1-3 to match the remote buttons.
|
||||
// EXT HT added to match remote button.
|
||||
"EXT HT",
|
||||
"M1",
|
||||
"M2",
|
||||
"M3",
|
||||
|
||||
@@ -113,7 +113,6 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
||||
this->conn_id = param->open.conn_id;
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
@@ -123,10 +122,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
|
||||
if (this->conn_id != param->connect.conn_id) {
|
||||
ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
|
||||
this->address_str().c_str(), param->connect.conn_id, this->conn_id);
|
||||
}
|
||||
this->conn_id = param->connect.conn_id;
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
|
||||
@@ -187,10 +183,9 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
descr->uuid.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
uint16_t notify_en = 1;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
uint8_t notify_en = 1;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
|
||||
¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ OVERSAMPLING_OPTIONS = {
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"32x": Oversampling.OVERSAMPLING_X32,
|
||||
}
|
||||
|
||||
IIRFilter = bmp3xx_ns.enum("IIRFilter")
|
||||
|
||||
@@ -79,7 +79,7 @@ async def to_code(config):
|
||||
cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
|
||||
cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
|
||||
|
||||
cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID]))
|
||||
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
|
||||
|
||||
# DSMR Parser
|
||||
cg.add_library("glmnet/Dsmr", "0.5")
|
||||
|
||||
@@ -109,10 +109,6 @@ void I2SAudioMediaPlayer::setup() {
|
||||
this->audio_ = make_unique<Audio>(false);
|
||||
this->audio_->setPinout(this->bclk_pin_, this->lrclk_pin_, this->dout_pin_);
|
||||
this->audio_->forceMono(this->external_dac_channels_ == 1);
|
||||
if (this->mute_pin_ != nullptr) {
|
||||
this->mute_pin_->setup();
|
||||
this->mute_pin_->digital_write(false);
|
||||
}
|
||||
}
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
||||
}
|
||||
|
||||
@@ -178,8 +178,7 @@ void Logger::pre_setup() {
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#endif
|
||||
break;
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
|
||||
@@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.coroutine import coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
@@ -20,9 +20,6 @@ MediaPlayer = media_player_ns.class_("MediaPlayer")
|
||||
PlayAction = media_player_ns.class_(
|
||||
"PlayAction", automation.Action, cg.Parented.template(MediaPlayer)
|
||||
)
|
||||
PlayMediaAction = media_player_ns.class_(
|
||||
"PlayMediaAction", automation.Action, cg.Parented.template(MediaPlayer)
|
||||
)
|
||||
ToggleAction = media_player_ns.class_(
|
||||
"ToggleAction", automation.Action, cg.Parented.template(MediaPlayer)
|
||||
)
|
||||
@@ -42,35 +39,11 @@ VolumeSetAction = media_player_ns.class_(
|
||||
"VolumeSetAction", automation.Action, cg.Parented.template(MediaPlayer)
|
||||
)
|
||||
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_ON_IDLE = "on_idle"
|
||||
CONF_ON_PLAY = "on_play"
|
||||
CONF_ON_PAUSE = "on_pause"
|
||||
CONF_MEDIA_URL = "media_url"
|
||||
|
||||
StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template())
|
||||
IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template())
|
||||
PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template())
|
||||
PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template())
|
||||
IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition)
|
||||
IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition)
|
||||
|
||||
|
||||
async def setup_media_player_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_IDLE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PLAY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PAUSE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
async def register_media_player(var, config):
|
||||
@@ -80,54 +53,12 @@ async def register_media_player(var, config):
|
||||
await setup_media_player_core_(var, config)
|
||||
|
||||
|
||||
MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_IDLE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdleTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PLAY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PlayTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PAUSE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.Schema({}))
|
||||
|
||||
|
||||
MEDIA_PLAYER_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(MediaPlayer)})
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"media_player.play_media",
|
||||
PlayMediaAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(MediaPlayer),
|
||||
cv.Required(CONF_MEDIA_URL): cv.templatable(cv.url),
|
||||
},
|
||||
key=CONF_MEDIA_URL,
|
||||
),
|
||||
)
|
||||
async def media_player_play_media_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
media_url = await cg.templatable(config[CONF_MEDIA_URL], args, cg.std_string)
|
||||
cg.add(var.set_media_url(media_url))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action("media_player.play", PlayAction, MEDIA_PLAYER_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"media_player.toggle", ToggleAction, MEDIA_PLAYER_ACTION_SCHEMA
|
||||
@@ -142,12 +73,6 @@ async def media_player_play_media_action(config, action_id, template_arg, args):
|
||||
@automation.register_action(
|
||||
"media_player.volume_down", VolumeDownAction, MEDIA_PLAYER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_condition(
|
||||
"media_player.is_idle", IsIdleCondition, MEDIA_PLAYER_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_condition(
|
||||
"media_player.is_playing", IsPlayingCondition, MEDIA_PLAYER_ACTION_SCHEMA
|
||||
)
|
||||
async def media_player_action(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
|
||||
@@ -14,17 +14,6 @@ namespace media_player {
|
||||
} \
|
||||
};
|
||||
|
||||
#define MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(TRIGGER_CLASS, TRIGGER_STATE) \
|
||||
class TRIGGER_CLASS : public Trigger<> { \
|
||||
public: \
|
||||
explicit TRIGGER_CLASS(MediaPlayer *player) { \
|
||||
player->add_on_state_callback([this, player]() { \
|
||||
if (player->state == MediaPlayerState::MEDIA_PLAYER_STATE_##TRIGGER_STATE) \
|
||||
this->trigger(); \
|
||||
}); \
|
||||
} \
|
||||
};
|
||||
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(PlayAction, PLAY)
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(PauseAction, PAUSE)
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(StopAction, STOP)
|
||||
@@ -32,36 +21,10 @@ MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(ToggleAction, TOGGLE)
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeUpAction, VOLUME_UP)
|
||||
MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeDownAction, VOLUME_DOWN)
|
||||
|
||||
template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> {
|
||||
TEMPLATABLE_VALUE(std::string, media_url)
|
||||
void play(Ts... x) override { this->parent_->make_call().set_media_url(this->media_url_.value(x...)).perform(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class VolumeSetAction : public Action<Ts...>, public Parented<MediaPlayer> {
|
||||
TEMPLATABLE_VALUE(float, volume)
|
||||
void play(Ts... x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); }
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit StateTrigger(MediaPlayer *player) {
|
||||
player->add_on_state_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE)
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING)
|
||||
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED)
|
||||
|
||||
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsPlayingCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; }
|
||||
};
|
||||
|
||||
} // namespace media_player
|
||||
} // namespace esphome
|
||||
|
||||
@@ -187,7 +187,7 @@ void MQTTClientComponent::start_connect_() {
|
||||
|
||||
this->mqtt_backend_.set_credentials(username, password);
|
||||
|
||||
this->mqtt_backend_.set_server(this->credentials_.address.c_str(), this->credentials_.port);
|
||||
this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port);
|
||||
if (!this->last_will_.topic.empty()) {
|
||||
this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
|
||||
this->last_will_.payload.c_str());
|
||||
|
||||
@@ -140,7 +140,5 @@ async def to_code(config):
|
||||
)
|
||||
)
|
||||
cg.add_library(
|
||||
None,
|
||||
None,
|
||||
"https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1",
|
||||
None, None, "https://github.com/Sensirion/arduino-gas-index-algorithm.git"
|
||||
)
|
||||
|
||||
@@ -170,8 +170,8 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) {
|
||||
// much
|
||||
if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) {
|
||||
voc_algorithm_.get_states(this->voc_state0_, this->voc_state1_);
|
||||
if (std::abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF ||
|
||||
std::abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) {
|
||||
if ((uint32_t) abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF ||
|
||||
(uint32_t) abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) {
|
||||
this->seconds_since_last_store_ = 0;
|
||||
this->voc_baselines_storage_.state0 = this->voc_state0_;
|
||||
this->voc_baselines_storage_.state1 = this->voc_state1_;
|
||||
|
||||
@@ -49,7 +49,7 @@ static const uint16_t SPG41_SELFTEST_TIME = 320; // 320 ms for self test
|
||||
static const uint16_t SGP40_MEASURE_TIME = 30;
|
||||
static const uint16_t SGP41_MEASURE_TIME = 55;
|
||||
// Store anyway if the baseline difference exceeds the max storage diff value
|
||||
const float MAXIMUM_STORAGE_DIFF = 50.0f;
|
||||
const uint32_t MAXIMUM_STORAGE_DIFF = 50;
|
||||
|
||||
class SGP4xComponent;
|
||||
|
||||
@@ -120,8 +120,8 @@ class SGP4xComponent : public PollingComponent, public sensor::Sensor, public se
|
||||
sensor::Sensor *voc_sensor_{nullptr};
|
||||
VOCGasIndexAlgorithm voc_algorithm_;
|
||||
optional<GasTuning> voc_tuning_params_;
|
||||
float voc_state0_;
|
||||
float voc_state1_;
|
||||
int32_t voc_state0_;
|
||||
int32_t voc_state1_;
|
||||
int32_t voc_index_ = 0;
|
||||
|
||||
sensor::Sensor *nox_sensor_{nullptr};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "tuya.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ void VL53L0XSensor::setup() {
|
||||
|
||||
ESP_LOGD(TAG, "'%s' - setup END", this->name_.c_str());
|
||||
}
|
||||
|
||||
void VL53L0XSensor::update() {
|
||||
if (this->initiated_read_ || this->waiting_for_interrupt_) {
|
||||
this->publish_state(NAN);
|
||||
@@ -280,6 +281,7 @@ void VL53L0XSensor::update() {
|
||||
this->initiated_read_ = true;
|
||||
// wait for timeout
|
||||
}
|
||||
|
||||
void VL53L0XSensor::loop() {
|
||||
if (this->initiated_read_) {
|
||||
if (reg(0x00).get() & 0x01) {
|
||||
@@ -311,5 +313,222 @@ void VL53L0XSensor::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VL53L0XSensor::get_measurement_timing_budget_() {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
|
||||
uint16_t start_overhead = 1910;
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
|
||||
// "Start and end overhead times always present"
|
||||
uint32_t budget_us = start_overhead + end_overhead;
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc)
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
|
||||
if (enables.dss) {
|
||||
budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
} else if (enables.msrc) {
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range)
|
||||
budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
|
||||
if (enables.final_range)
|
||||
budget_us += (timeouts.final_range_us + final_range_overhead);
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
return budget_us;
|
||||
}
|
||||
|
||||
bool VL53L0XSensor::set_measurement_timing_budget_(uint32_t budget_us) {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
|
||||
uint16_t start_overhead = 1320; // note that this is different than the value in get_
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
|
||||
uint32_t min_timing_budget = 20000;
|
||||
|
||||
if (budget_us < min_timing_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t used_budget_us = start_overhead + end_overhead;
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
}
|
||||
|
||||
if (enables.dss) {
|
||||
used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
} else if (enables.msrc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range) {
|
||||
used_budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
}
|
||||
|
||||
if (enables.final_range) {
|
||||
used_budget_us += final_range_overhead;
|
||||
|
||||
// "Note that the final range timeout is determined by the timing
|
||||
// budget and the sum of all other timeouts within the sequence.
|
||||
// If there is no room for the final range timeout, then an error
|
||||
// will be set. Otherwise the remaining time will be applied to
|
||||
// the final range."
|
||||
|
||||
if (used_budget_us > budget_us) {
|
||||
// "Requested timeout too big."
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t final_range_timeout_us = budget_us - used_budget_us;
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
|
||||
|
||||
// "For the final range timeout, the pre-range timeout
|
||||
// must be added. To do this both final and pre-range
|
||||
// timeouts must be expressed in macro periods MClks
|
||||
// because they have different vcsel periods."
|
||||
|
||||
uint16_t final_range_timeout_mclks =
|
||||
timeout_microseconds_to_mclks_(final_range_timeout_us, timeouts.final_range_vcsel_period_pclks);
|
||||
|
||||
if (enables.pre_range) {
|
||||
final_range_timeout_mclks += timeouts.pre_range_mclks;
|
||||
}
|
||||
|
||||
write_byte_16(0x71, encode_timeout_(final_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VL53L0XSensor::get_sequence_step_enables_(SequenceStepEnables *enables) {
|
||||
uint8_t sequence_config = reg(0x01).get();
|
||||
enables->tcc = (sequence_config >> 4) & 0x1;
|
||||
enables->dss = (sequence_config >> 3) & 0x1;
|
||||
enables->msrc = (sequence_config >> 2) & 0x1;
|
||||
enables->pre_range = (sequence_config >> 6) & 0x1;
|
||||
enables->final_range = (sequence_config >> 7) & 0x1;
|
||||
}
|
||||
|
||||
void VL53L0XSensor::get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts) {
|
||||
timeouts->pre_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_PRE_RANGE);
|
||||
|
||||
timeouts->msrc_dss_tcc_mclks = reg(0x46).get() + 1;
|
||||
timeouts->msrc_dss_tcc_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->msrc_dss_tcc_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
uint16_t value;
|
||||
read_byte_16(0x51, &value);
|
||||
timeouts->pre_range_mclks = decode_timeout_(value);
|
||||
timeouts->pre_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->pre_range_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
timeouts->final_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_FINAL_RANGE);
|
||||
|
||||
read_byte_16(0x71, &value);
|
||||
timeouts->final_range_mclks = decode_timeout_(value);
|
||||
|
||||
if (enables->pre_range) {
|
||||
timeouts->final_range_mclks -= timeouts->pre_range_mclks;
|
||||
}
|
||||
|
||||
timeouts->final_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->final_range_mclks, timeouts->final_range_vcsel_period_pclks);
|
||||
}
|
||||
|
||||
uint8_t VL53L0XSensor::get_vcsel_pulse_period_(VcselPeriodType type) {
|
||||
uint8_t vcsel;
|
||||
if (type == VCSEL_PERIOD_PRE_RANGE) {
|
||||
vcsel = reg(0x50).get();
|
||||
} else if (type == VCSEL_PERIOD_FINAL_RANGE) {
|
||||
vcsel = reg(0x70).get();
|
||||
} else {
|
||||
return 255;
|
||||
}
|
||||
|
||||
return (vcsel + 1) << 1;
|
||||
}
|
||||
|
||||
uint32_t VL53L0XSensor::get_macro_period_(uint8_t vcsel_period_pclks) {
|
||||
return ((2304UL * vcsel_period_pclks * 1655UL) + 500UL) / 1000UL;
|
||||
}
|
||||
|
||||
uint32_t VL53L0XSensor::timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
|
||||
}
|
||||
|
||||
uint32_t VL53L0XSensor::timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
|
||||
}
|
||||
|
||||
uint16_t VL53L0XSensor::decode_timeout_(uint16_t reg_val) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint8_t msb = (reg_val >> 8) & 0xFF;
|
||||
uint8_t lsb = (reg_val >> 0) & 0xFF;
|
||||
return (uint16_t(lsb) << msb) + 1;
|
||||
}
|
||||
|
||||
uint16_t VL53L0XSensor::encode_timeout_(uint16_t timeout_mclks) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint32_t ls_byte = 0;
|
||||
uint16_t ms_byte = 0;
|
||||
|
||||
if (timeout_mclks <= 0)
|
||||
return 0;
|
||||
|
||||
ls_byte = timeout_mclks - 1;
|
||||
|
||||
while ((ls_byte & 0xFFFFFF00) > 0) {
|
||||
ls_byte >>= 1;
|
||||
ms_byte++;
|
||||
}
|
||||
|
||||
return (ms_byte << 8) | (ls_byte & 0xFF);
|
||||
}
|
||||
|
||||
bool VL53L0XSensor::perform_single_ref_calibration_(uint8_t vhv_init_byte) {
|
||||
reg(0x00) = 0x01 | vhv_init_byte; // VL53L0X_REG_SYSRANGE_MODE_START_STOP
|
||||
|
||||
uint32_t start = millis();
|
||||
while ((reg(0x13).get() & 0x07) == 0) {
|
||||
if (millis() - start > 1000)
|
||||
return false;
|
||||
yield();
|
||||
}
|
||||
|
||||
reg(0x0B) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace vl53l0x
|
||||
} // namespace esphome
|
||||
|
||||
@@ -21,6 +21,8 @@ struct SequenceStepTimeouts {
|
||||
uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us;
|
||||
};
|
||||
|
||||
enum VcselPeriodType { VCSEL_PERIOD_PRE_RANGE, VCSEL_PERIOD_FINAL_RANGE };
|
||||
|
||||
class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
VL53L0XSensor();
|
||||
@@ -39,222 +41,20 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c
|
||||
void set_enable_pin(GPIOPin *enable) { this->enable_pin_ = enable; }
|
||||
|
||||
protected:
|
||||
uint32_t get_measurement_timing_budget_() {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
uint32_t get_measurement_timing_budget_();
|
||||
bool set_measurement_timing_budget_(uint32_t budget_us);
|
||||
void get_sequence_step_enables_(SequenceStepEnables *enables);
|
||||
void get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts);
|
||||
uint8_t get_vcsel_pulse_period_(VcselPeriodType type);
|
||||
uint32_t get_macro_period_(uint8_t vcsel_period_pclks);
|
||||
|
||||
uint16_t start_overhead = 1910;
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
uint32_t timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks);
|
||||
uint32_t timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks);
|
||||
|
||||
// "Start and end overhead times always present"
|
||||
uint32_t budget_us = start_overhead + end_overhead;
|
||||
uint16_t decode_timeout_(uint16_t reg_val);
|
||||
uint16_t encode_timeout_(uint16_t timeout_mclks);
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc)
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
|
||||
if (enables.dss) {
|
||||
budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
} else if (enables.msrc) {
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range)
|
||||
budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
|
||||
if (enables.final_range)
|
||||
budget_us += (timeouts.final_range_us + final_range_overhead);
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
return budget_us;
|
||||
}
|
||||
|
||||
bool set_measurement_timing_budget_(uint32_t budget_us) {
|
||||
SequenceStepEnables enables{};
|
||||
SequenceStepTimeouts timeouts{};
|
||||
|
||||
uint16_t start_overhead = 1320; // note that this is different than the value in get_
|
||||
uint16_t end_overhead = 960;
|
||||
uint16_t msrc_overhead = 660;
|
||||
uint16_t tcc_overhead = 590;
|
||||
uint16_t dss_overhead = 690;
|
||||
uint16_t pre_range_overhead = 660;
|
||||
uint16_t final_range_overhead = 550;
|
||||
|
||||
uint32_t min_timing_budget = 20000;
|
||||
|
||||
if (budget_us < min_timing_budget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t used_budget_us = start_overhead + end_overhead;
|
||||
|
||||
get_sequence_step_enables_(&enables);
|
||||
get_sequence_step_timeouts_(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead);
|
||||
}
|
||||
|
||||
if (enables.dss) {
|
||||
used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead);
|
||||
} else if (enables.msrc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range) {
|
||||
used_budget_us += (timeouts.pre_range_us + pre_range_overhead);
|
||||
}
|
||||
|
||||
if (enables.final_range) {
|
||||
used_budget_us += final_range_overhead;
|
||||
|
||||
// "Note that the final range timeout is determined by the timing
|
||||
// budget and the sum of all other timeouts within the sequence.
|
||||
// If there is no room for the final range timeout, then an error
|
||||
// will be set. Otherwise the remaining time will be applied to
|
||||
// the final range."
|
||||
|
||||
if (used_budget_us > budget_us) {
|
||||
// "Requested timeout too big."
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t final_range_timeout_us = budget_us - used_budget_us;
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
|
||||
|
||||
// "For the final range timeout, the pre-range timeout
|
||||
// must be added. To do this both final and pre-range
|
||||
// timeouts must be expressed in macro periods MClks
|
||||
// because they have different vcsel periods."
|
||||
|
||||
uint16_t final_range_timeout_mclks =
|
||||
timeout_microseconds_to_mclks_(final_range_timeout_us, timeouts.final_range_vcsel_period_pclks);
|
||||
|
||||
if (enables.pre_range) {
|
||||
final_range_timeout_mclks += timeouts.pre_range_mclks;
|
||||
}
|
||||
|
||||
write_byte_16(0x71, encode_timeout_(final_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
|
||||
measurement_timing_budget_us_ = budget_us; // store for internal reuse
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void get_sequence_step_enables_(SequenceStepEnables *enables) {
|
||||
uint8_t sequence_config = reg(0x01).get();
|
||||
enables->tcc = (sequence_config >> 4) & 0x1;
|
||||
enables->dss = (sequence_config >> 3) & 0x1;
|
||||
enables->msrc = (sequence_config >> 2) & 0x1;
|
||||
enables->pre_range = (sequence_config >> 6) & 0x1;
|
||||
enables->final_range = (sequence_config >> 7) & 0x1;
|
||||
}
|
||||
|
||||
enum VcselPeriodType { VCSEL_PERIOD_PRE_RANGE, VCSEL_PERIOD_FINAL_RANGE };
|
||||
|
||||
void get_sequence_step_timeouts_(SequenceStepEnables const *enables, SequenceStepTimeouts *timeouts) {
|
||||
timeouts->pre_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_PRE_RANGE);
|
||||
|
||||
timeouts->msrc_dss_tcc_mclks = reg(0x46).get() + 1;
|
||||
timeouts->msrc_dss_tcc_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->msrc_dss_tcc_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
uint16_t value;
|
||||
read_byte_16(0x51, &value);
|
||||
timeouts->pre_range_mclks = decode_timeout_(value);
|
||||
timeouts->pre_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->pre_range_mclks, timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
timeouts->final_range_vcsel_period_pclks = get_vcsel_pulse_period_(VCSEL_PERIOD_FINAL_RANGE);
|
||||
|
||||
read_byte_16(0x71, &value);
|
||||
timeouts->final_range_mclks = decode_timeout_(value);
|
||||
|
||||
if (enables->pre_range) {
|
||||
timeouts->final_range_mclks -= timeouts->pre_range_mclks;
|
||||
}
|
||||
|
||||
timeouts->final_range_us =
|
||||
timeout_mclks_to_microseconds_(timeouts->final_range_mclks, timeouts->final_range_vcsel_period_pclks);
|
||||
}
|
||||
|
||||
uint8_t get_vcsel_pulse_period_(VcselPeriodType type) {
|
||||
uint8_t vcsel;
|
||||
if (type == VCSEL_PERIOD_PRE_RANGE) {
|
||||
vcsel = reg(0x50).get();
|
||||
} else if (type == VCSEL_PERIOD_FINAL_RANGE) {
|
||||
vcsel = reg(0x70).get();
|
||||
} else {
|
||||
return 255;
|
||||
}
|
||||
|
||||
return (vcsel + 1) << 1;
|
||||
}
|
||||
|
||||
uint32_t get_macro_period_(uint8_t vcsel_period_pclks) {
|
||||
return ((2304UL * vcsel_period_pclks * 1655UL) + 500UL) / 1000UL;
|
||||
}
|
||||
|
||||
uint32_t timeout_mclks_to_microseconds_(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return ((timeout_period_mclks * macro_period_ns) + (macro_period_ns / 2)) / 1000;
|
||||
}
|
||||
uint32_t timeout_microseconds_to_mclks_(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) {
|
||||
uint32_t macro_period_ns = get_macro_period_(vcsel_period_pclks);
|
||||
return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
|
||||
}
|
||||
|
||||
uint16_t decode_timeout_(uint16_t reg_val) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint8_t msb = (reg_val >> 8) & 0xFF;
|
||||
uint8_t lsb = (reg_val >> 0) & 0xFF;
|
||||
return (uint16_t(lsb) << msb) + 1;
|
||||
}
|
||||
uint16_t encode_timeout_(uint16_t timeout_mclks) {
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint32_t ls_byte = 0;
|
||||
uint16_t ms_byte = 0;
|
||||
|
||||
if (timeout_mclks <= 0)
|
||||
return 0;
|
||||
|
||||
ls_byte = timeout_mclks - 1;
|
||||
|
||||
while ((ls_byte & 0xFFFFFF00) > 0) {
|
||||
ls_byte >>= 1;
|
||||
ms_byte++;
|
||||
}
|
||||
|
||||
return (ms_byte << 8) | (ls_byte & 0xFF);
|
||||
}
|
||||
|
||||
bool perform_single_ref_calibration_(uint8_t vhv_init_byte) {
|
||||
reg(0x00) = 0x01 | vhv_init_byte; // VL53L0X_REG_SYSRANGE_MODE_START_STOP
|
||||
|
||||
uint32_t start = millis();
|
||||
while ((reg(0x13).get() & 0x07) == 0) {
|
||||
if (millis() - start > 1000)
|
||||
return false;
|
||||
yield();
|
||||
}
|
||||
|
||||
reg(0x0B) = 0x01;
|
||||
reg(0x00) = 0x00;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool perform_single_ref_calibration_(uint8_t vhv_init_byte);
|
||||
|
||||
float signal_rate_limit_;
|
||||
bool long_range_;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2022.6.2"
|
||||
__version__ = "2022.7.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
|
||||
@@ -600,7 +600,7 @@ template<class T> class ExternalRAMAllocator {
|
||||
|
||||
ExternalRAMAllocator() = default;
|
||||
ExternalRAMAllocator(Flags flags) : flags_{flags} {}
|
||||
template<class U> constexpr ExternalRAMAllocator(const ExternalRAMAllocator<U> &other) : flags_{other.flags_} {}
|
||||
template<class U> constexpr ExternalRAMAllocator(const ExternalRAMAllocator<U> &other) : flags_{other.flags} {}
|
||||
|
||||
T *allocate(size_t n) {
|
||||
size_t size = n * sizeof(T);
|
||||
|
||||
@@ -40,7 +40,7 @@ lib_deps =
|
||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||
; This is using the repository until a new release is published to PlatformIO
|
||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git ; Sensirion Gas Index Algorithm Arduino Library
|
||||
build_flags =
|
||||
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
src_filter =
|
||||
|
||||
@@ -604,6 +604,14 @@ touchscreen:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: ["touch.x", "touch.y"]
|
||||
- media_player.play:
|
||||
- media_player.pause:
|
||||
- media_player.stop:
|
||||
- media_player.toggle:
|
||||
- media_player.volume_up:
|
||||
- media_player.volume_down:
|
||||
- media_player.volume_set: 50%
|
||||
|
||||
|
||||
media_player:
|
||||
- platform: i2s_audio
|
||||
@@ -613,20 +621,3 @@ media_player:
|
||||
i2s_dout_pin: GPIO25
|
||||
i2s_bclk_pin: GPIO27
|
||||
mute_pin: GPIO14
|
||||
on_state:
|
||||
- media_player.play:
|
||||
- media_player.play_media: http://localhost/media.mp3
|
||||
- media_player.play_media: !lambda 'return "http://localhost/media.mp3";'
|
||||
on_idle:
|
||||
- media_player.pause:
|
||||
on_play:
|
||||
- media_player.stop:
|
||||
on_pause:
|
||||
- media_player.toggle:
|
||||
- wait_until:
|
||||
media_player.is_idle:
|
||||
- wait_until:
|
||||
media_player.is_playing:
|
||||
- media_player.volume_up:
|
||||
- media_player.volume_down:
|
||||
- media_player.volume_set: 50%
|
||||
|
||||
Reference in New Issue
Block a user