mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 07:58:09 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
b36936ed84
17 changed files with 62 additions and 15 deletions
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
remove-stale-when-updated: true
|
remove-stale-when-updated: true
|
||||||
stale-pr-label: "stale"
|
stale-pr-label: "stale"
|
||||||
exempt-pr-labels: "no-stale"
|
exempt-pr-labels: "not-stale"
|
||||||
stale-pr-message: >
|
stale-pr-message: >
|
||||||
There hasn't been any activity on this pull request recently. This
|
There hasn't been any activity on this pull request recently. This
|
||||||
pull request has been automatically marked as stale because of that
|
pull request has been automatically marked as stale because of that
|
||||||
|
|
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
|
@ -53,8 +53,8 @@ jobs:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@nabucasa.com>
|
committer: esphomebot <esphome@nabucasa.com>
|
||||||
author: esphomebot <esphome@nabucasa.com>
|
author: esphomebot <esphome@nabucasa.com>
|
||||||
branch: sync/device-classes/
|
branch: sync/device-classes
|
||||||
branch-suffix: timestamp
|
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
title: "Synchronise Device Classes from Home Assistant"
|
title: "Synchronise Device Classes from Home Assistant"
|
||||||
body: ${{ steps.pr-template-body.outputs.body }}
|
body: ${{ steps.pr-template-body.outputs.body }}
|
||||||
|
token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}
|
||||||
|
|
|
@ -223,6 +223,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||||
msg.assumed_state = traits.get_is_assumed_state();
|
msg.assumed_state = traits.get_is_assumed_state();
|
||||||
msg.supports_position = traits.get_supports_position();
|
msg.supports_position = traits.get_supports_position();
|
||||||
msg.supports_tilt = traits.get_supports_tilt();
|
msg.supports_tilt = traits.get_supports_tilt();
|
||||||
|
msg.supports_stop = traits.get_supports_stop();
|
||||||
msg.device_class = cover->get_device_class();
|
msg.device_class = cover->get_device_class();
|
||||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||||
msg.icon = cover->get_icon();
|
msg.icon = cover->get_icon();
|
||||||
|
|
|
@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.COMPONENT_SCHEMA.extend(
|
cv.COMPONENT_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(KeyCollector),
|
cv.GenerateID(): cv.declare_id(KeyCollector),
|
||||||
cv.GenerateID(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
|
cv.Optional(CONF_SOURCE_ID): cv.use_id(key_provider.KeyProvider),
|
||||||
cv.Optional(CONF_MIN_LENGTH): cv.int_,
|
cv.Optional(CONF_MIN_LENGTH): cv.int_,
|
||||||
cv.Optional(CONF_MAX_LENGTH): cv.int_,
|
cv.Optional(CONF_MAX_LENGTH): cv.int_,
|
||||||
cv.Optional(CONF_START_KEYS): cv.string,
|
cv.Optional(CONF_START_KEYS): cv.string,
|
||||||
|
@ -55,6 +55,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
if CONF_SOURCE_ID in config:
|
||||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||||
cg.add(var.set_provider(source))
|
cg.add(var.set_provider(source))
|
||||||
if CONF_MIN_LENGTH in config:
|
if CONF_MIN_LENGTH in config:
|
||||||
|
|
|
@ -52,6 +52,8 @@ void KeyCollector::clear(bool progress_update) {
|
||||||
this->progress_trigger_->trigger(this->result_, 0);
|
this->progress_trigger_->trigger(this->result_, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeyCollector::send_key(uint8_t key) { this->key_pressed_(key); }
|
||||||
|
|
||||||
void KeyCollector::key_pressed_(uint8_t key) {
|
void KeyCollector::key_pressed_(uint8_t key) {
|
||||||
this->last_key_time_ = millis();
|
this->last_key_time_ = millis();
|
||||||
if (!this->start_keys_.empty() && !this->start_key_) {
|
if (!this->start_keys_.empty() && !this->start_key_) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ class KeyCollector : public Component {
|
||||||
void set_timeout(int timeout) { this->timeout_ = timeout; };
|
void set_timeout(int timeout) { this->timeout_ = timeout; };
|
||||||
|
|
||||||
void clear(bool progress_update = true);
|
void clear(bool progress_update = true);
|
||||||
|
void send_key(uint8_t key);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void key_pressed_(uint8_t key);
|
void key_pressed_(uint8_t key);
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PulseLightEffect : public LightEffect {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto call = this->state_->turn_on();
|
auto call = this->state_->turn_on();
|
||||||
float out = this->on_ ? 1.0 : 0.0;
|
float out = this->on_ ? this->max_brightness : this->min_brightness;
|
||||||
call.set_brightness_if_supported(out);
|
call.set_brightness_if_supported(out);
|
||||||
this->on_ = !this->on_;
|
this->on_ = !this->on_;
|
||||||
call.set_transition_length_if_supported(this->transition_length_);
|
call.set_transition_length_if_supported(this->transition_length_);
|
||||||
|
@ -41,11 +41,18 @@ class PulseLightEffect : public LightEffect {
|
||||||
|
|
||||||
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
||||||
|
|
||||||
|
void set_min_max_brightness(float min, float max) {
|
||||||
|
this->min_brightness = min;
|
||||||
|
this->max_brightness = max;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool on_ = false;
|
bool on_ = false;
|
||||||
uint32_t last_color_change_{0};
|
uint32_t last_color_change_{0};
|
||||||
uint32_t transition_length_{};
|
uint32_t transition_length_{};
|
||||||
uint32_t update_interval_{};
|
uint32_t update_interval_{};
|
||||||
|
float min_brightness{0.0};
|
||||||
|
float max_brightness{1.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
|
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
|
||||||
|
|
|
@ -28,6 +28,8 @@ from esphome.const import (
|
||||||
CONF_NUM_LEDS,
|
CONF_NUM_LEDS,
|
||||||
CONF_RANDOM,
|
CONF_RANDOM,
|
||||||
CONF_SEQUENCE,
|
CONF_SEQUENCE,
|
||||||
|
CONF_MAX_BRIGHTNESS,
|
||||||
|
CONF_MIN_BRIGHTNESS,
|
||||||
)
|
)
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
from .types import (
|
from .types import (
|
||||||
|
@ -174,12 +176,19 @@ async def automation_effect_to_code(config, effect_id):
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_UPDATE_INTERVAL, default="1s"
|
CONF_UPDATE_INTERVAL, default="1s"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_MIN_BRIGHTNESS, default="0%"): cv.percentage,
|
||||||
|
cv.Optional(CONF_MAX_BRIGHTNESS, default="100%"): cv.percentage,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def pulse_effect_to_code(config, effect_id):
|
async def pulse_effect_to_code(config, effect_id):
|
||||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
|
||||||
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
||||||
|
cg.add(
|
||||||
|
effect.set_min_max_brightness(
|
||||||
|
config[CONF_MIN_BRIGHTNESS], config[CONF_MAX_BRIGHTNESS]
|
||||||
|
)
|
||||||
|
)
|
||||||
return effect
|
return effect
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ from esphome.const import (
|
||||||
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
DEVICE_CLASS_VOLUME_STORAGE,
|
DEVICE_CLASS_VOLUME_STORAGE,
|
||||||
|
@ -109,6 +110,7 @@ DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
DEVICE_CLASS_SULPHUR_DIOXIDE,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
DEVICE_CLASS_VOLUME_STORAGE,
|
DEVICE_CLASS_VOLUME_STORAGE,
|
||||||
|
|
|
@ -119,7 +119,7 @@ CONFIG_SCHEMA = (
|
||||||
device_class=DEVICE_CLASS_PM10,
|
device_class=DEVICE_CLASS_PM10,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.time_period_in_seconds_,
|
cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.update_interval,
|
||||||
cv.Optional(CONF_VOC): sensor.sensor_schema(
|
cv.Optional(CONF_VOC): sensor.sensor_schema(
|
||||||
icon=ICON_RADIATOR,
|
icon=ICON_RADIATOR,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
|
|
|
@ -73,6 +73,7 @@ from esphome.const import (
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
DEVICE_CLASS_VOLUME_STORAGE,
|
DEVICE_CLASS_VOLUME_STORAGE,
|
||||||
|
@ -129,6 +130,7 @@ DEVICE_CLASSES = [
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
DEVICE_CLASS_VOLUME_STORAGE,
|
DEVICE_CLASS_VOLUME_STORAGE,
|
||||||
|
|
|
@ -23,6 +23,8 @@ from esphome.const import (
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
|
CONF_MIN_BRIGHTNESS,
|
||||||
|
CONF_MAX_BRIGHTNESS,
|
||||||
)
|
)
|
||||||
from esphome.core import HexInt, CORE
|
from esphome.core import HexInt, CORE
|
||||||
|
|
||||||
|
@ -41,8 +43,7 @@ CONF_UPDATE = "update"
|
||||||
CONF_LEADING_EDGE = "leading_edge"
|
CONF_LEADING_EDGE = "leading_edge"
|
||||||
CONF_WARMUP_BRIGHTNESS = "warmup_brightness"
|
CONF_WARMUP_BRIGHTNESS = "warmup_brightness"
|
||||||
# CONF_WARMUP_TIME = "warmup_time"
|
# CONF_WARMUP_TIME = "warmup_time"
|
||||||
CONF_MIN_BRIGHTNESS = "min_brightness"
|
|
||||||
CONF_MAX_BRIGHTNESS = "max_brightness"
|
|
||||||
|
|
||||||
CONF_NRST_PIN = "nrst_pin"
|
CONF_NRST_PIN = "nrst_pin"
|
||||||
CONF_BOOT0_PIN = "boot0_pin"
|
CONF_BOOT0_PIN = "boot0_pin"
|
||||||
|
|
|
@ -86,10 +86,26 @@ void ESP32ArduinoUARTComponent::setup() {
|
||||||
is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1;
|
is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1;
|
||||||
is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3;
|
is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3;
|
||||||
#endif
|
#endif
|
||||||
if (is_default_tx && is_default_rx) {
|
static uint8_t next_uart_num = 0;
|
||||||
|
if (is_default_tx && is_default_rx && next_uart_num == 0) {
|
||||||
this->hw_serial_ = &Serial;
|
this->hw_serial_ = &Serial;
|
||||||
|
next_uart_num++;
|
||||||
} else {
|
} else {
|
||||||
static uint8_t next_uart_num = 1;
|
#ifdef USE_LOGGER
|
||||||
|
// The logger doesn't use this UART component, instead it targets the UARTs
|
||||||
|
// directly (i.e. Serial/Serial0, Serial1, and Serial2). If the logger is
|
||||||
|
// enabled, skip the UART that it is configured to use.
|
||||||
|
if (logger::global_logger->get_baud_rate() > 0 && logger::global_logger->get_uart() == next_uart_num) {
|
||||||
|
next_uart_num++;
|
||||||
|
}
|
||||||
|
#endif // USE_LOGGER
|
||||||
|
|
||||||
|
if (next_uart_num >= UART_NUM_MAX) {
|
||||||
|
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->number_ = next_uart_num;
|
this->number_ = next_uart_num;
|
||||||
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
|
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||||
|
|
||||||
|
#include <driver/uart.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
|
@ -377,6 +377,7 @@ CONF_MAKE_ID = "make_id"
|
||||||
CONF_MANUAL_IP = "manual_ip"
|
CONF_MANUAL_IP = "manual_ip"
|
||||||
CONF_MANUFACTURER_ID = "manufacturer_id"
|
CONF_MANUFACTURER_ID = "manufacturer_id"
|
||||||
CONF_MASK_DISTURBER = "mask_disturber"
|
CONF_MASK_DISTURBER = "mask_disturber"
|
||||||
|
CONF_MAX_BRIGHTNESS = "max_brightness"
|
||||||
CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
|
CONF_MAX_COOLING_RUN_TIME = "max_cooling_run_time"
|
||||||
CONF_MAX_CURRENT = "max_current"
|
CONF_MAX_CURRENT = "max_current"
|
||||||
CONF_MAX_DURATION = "max_duration"
|
CONF_MAX_DURATION = "max_duration"
|
||||||
|
@ -396,6 +397,7 @@ CONF_MEDIUM = "medium"
|
||||||
CONF_MEMORY_BLOCKS = "memory_blocks"
|
CONF_MEMORY_BLOCKS = "memory_blocks"
|
||||||
CONF_METHOD = "method"
|
CONF_METHOD = "method"
|
||||||
CONF_MICROPHONE = "microphone"
|
CONF_MICROPHONE = "microphone"
|
||||||
|
CONF_MIN_BRIGHTNESS = "min_brightness"
|
||||||
CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time"
|
CONF_MIN_COOLING_OFF_TIME = "min_cooling_off_time"
|
||||||
CONF_MIN_COOLING_RUN_TIME = "min_cooling_run_time"
|
CONF_MIN_COOLING_RUN_TIME = "min_cooling_run_time"
|
||||||
CONF_MIN_FAN_MODE_SWITCHING_TIME = "min_fan_mode_switching_time"
|
CONF_MIN_FAN_MODE_SWITCHING_TIME = "min_fan_mode_switching_time"
|
||||||
|
@ -1003,6 +1005,7 @@ DEVICE_CLASS_TIMESTAMP = "timestamp"
|
||||||
DEVICE_CLASS_UPDATE = "update"
|
DEVICE_CLASS_UPDATE = "update"
|
||||||
DEVICE_CLASS_VIBRATION = "vibration"
|
DEVICE_CLASS_VIBRATION = "vibration"
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
|
||||||
DEVICE_CLASS_VOLTAGE = "voltage"
|
DEVICE_CLASS_VOLTAGE = "voltage"
|
||||||
DEVICE_CLASS_VOLUME = "volume"
|
DEVICE_CLASS_VOLUME = "volume"
|
||||||
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
|
DEVICE_CLASS_VOLUME_STORAGE = "volume_storage"
|
||||||
|
|
|
@ -3,14 +3,14 @@ PyYAML==6.0
|
||||||
paho-mqtt==1.6.1
|
paho-mqtt==1.6.1
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
tornado==6.3.1
|
tornado==6.3.1
|
||||||
tzlocal==4.2 # from time
|
tzlocal==5.0.1 # from time
|
||||||
tzdata>=2021.1 # from time
|
tzdata>=2021.1 # from time
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
platformio==6.1.6 # When updating platformio, also update Dockerfile
|
platformio==6.1.6 # When updating platformio, also update Dockerfile
|
||||||
esptool==4.5.1
|
esptool==4.5.1
|
||||||
click==8.1.3
|
click==8.1.3
|
||||||
esphome-dashboard==20230214.0
|
esphome-dashboard==20230516.0
|
||||||
aioesphomeapi==13.7.2
|
aioesphomeapi==13.7.5
|
||||||
zeroconf==0.60.0
|
zeroconf==0.60.0
|
||||||
|
|
||||||
# esp-idf requires this, but doesn't bundle it by default
|
# esp-idf requires this, but doesn't bundle it by default
|
||||||
|
|
|
@ -489,6 +489,7 @@ sensor:
|
||||||
offset: 0
|
offset: 0
|
||||||
normalized_offset_slope: 0
|
normalized_offset_slope: 0
|
||||||
time_constant: 0
|
time_constant: 0
|
||||||
|
auto_cleaning_interval: 604800s
|
||||||
acceleration_mode: low
|
acceleration_mode: low
|
||||||
store_baseline: true
|
store_baseline: true
|
||||||
address: 0x69
|
address: 0x69
|
||||||
|
|
Loading…
Reference in a new issue