mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 12:06:26 +01:00
commit
ec683fc227
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@trvrnrth"]
|
CODEOWNERS = ["@trvrnrth"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@ -44,7 +45,8 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||||
): cv.positive_time_period_minutes,
|
): cv.positive_time_period_minutes,
|
||||||
}
|
},
|
||||||
|
cv.only_with_arduino,
|
||||||
).extend(i2c.i2c_device_schema(0x76))
|
).extend(i2c.i2c_device_schema(0x76))
|
||||||
|
|
||||||
|
|
||||||
@ -60,5 +62,9 @@ async def to_code(config):
|
|||||||
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CORE.is_esp32:
|
||||||
|
# Although this component does not use SPI, the BSEC library requires the SPI library
|
||||||
|
cg.add_library("SPI", None)
|
||||||
|
|
||||||
cg.add_define("USE_BSEC")
|
cg.add_define("USE_BSEC")
|
||||||
cg.add_library("BSEC Software Library", "1.6.1480")
|
cg.add_library("BSEC Software Library", "1.6.1480")
|
||||||
|
@ -21,12 +21,16 @@ from esphome.core import CORE, HexInt
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
from .const import (
|
from .const import ( # noqa
|
||||||
KEY_BOARD,
|
KEY_BOARD,
|
||||||
KEY_ESP32,
|
KEY_ESP32,
|
||||||
KEY_SDKCONFIG_OPTIONS,
|
KEY_SDKCONFIG_OPTIONS,
|
||||||
KEY_VARIANT,
|
KEY_VARIANT,
|
||||||
|
VARIANT_ESP32,
|
||||||
|
VARIANT_ESP32S2,
|
||||||
|
VARIANT_ESP32S3,
|
||||||
VARIANT_ESP32C3,
|
VARIANT_ESP32C3,
|
||||||
|
VARIANT_ESP32H2,
|
||||||
VARIANTS,
|
VARIANTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ optional<LightColorValues> AddressableLightTransformer::apply() {
|
|||||||
// dynamically-calculated alpha values to match the look.
|
// dynamically-calculated alpha values to match the look.
|
||||||
|
|
||||||
float denom = (1.0f - smoothed_progress);
|
float denom = (1.0f - smoothed_progress);
|
||||||
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||||
|
|
||||||
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
||||||
// We solve this by accumulating the fractional part of the alpha over time.
|
// We solve this by accumulating the fractional part of the alpha over time.
|
||||||
|
@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight {
|
|||||||
|
|
||||||
void clear_effect_data() override { this->wrapper_state_[4] = 0; }
|
void clear_effect_data() override { this->wrapper_state_[4] = 0; }
|
||||||
|
|
||||||
light::LightTraits get_traits() override { return this->light_state_->get_traits(); }
|
light::LightTraits get_traits() override {
|
||||||
|
LightTraits traits;
|
||||||
|
|
||||||
|
// Choose which color mode to use.
|
||||||
|
// This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition.
|
||||||
|
ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE,
|
||||||
|
ColorMode::RGB_COLD_WARM_WHITE,
|
||||||
|
ColorMode::RGB_COLOR_TEMPERATURE,
|
||||||
|
ColorMode::RGB,
|
||||||
|
ColorMode::WHITE,
|
||||||
|
ColorMode::COLD_WARM_WHITE,
|
||||||
|
ColorMode::COLOR_TEMPERATURE,
|
||||||
|
ColorMode::BRIGHTNESS,
|
||||||
|
ColorMode::ON_OFF,
|
||||||
|
ColorMode::UNKNOWN};
|
||||||
|
|
||||||
|
LightTraits parent_traits = this->light_state_->get_traits();
|
||||||
|
for (auto cm : color_mode_precedence) {
|
||||||
|
if (parent_traits.supports_color_mode(cm)) {
|
||||||
|
this->color_mode_ = cm;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report a color mode that's compatible with both the partition and the underlying light
|
||||||
|
switch (this->color_mode_) {
|
||||||
|
case ColorMode::RGB_WHITE:
|
||||||
|
case ColorMode::RGB_COLD_WARM_WHITE:
|
||||||
|
case ColorMode::RGB_COLOR_TEMPERATURE:
|
||||||
|
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ColorMode::RGB:
|
||||||
|
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ColorMode::WHITE:
|
||||||
|
case ColorMode::COLD_WARM_WHITE:
|
||||||
|
case ColorMode::COLOR_TEMPERATURE:
|
||||||
|
case ColorMode::BRIGHTNESS:
|
||||||
|
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ColorMode::ON_OFF:
|
||||||
|
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
traits.set_supported_color_modes({light::ColorMode::UNKNOWN});
|
||||||
|
}
|
||||||
|
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
void write_state(light::LightState *state) override {
|
void write_state(light::LightState *state) override {
|
||||||
|
// Don't overwrite state if the underlying light is turned on
|
||||||
|
if (this->light_state_->remote_values.is_on()) {
|
||||||
|
this->mark_shown_();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float gamma = this->light_state_->get_gamma_correct();
|
float gamma = this->light_state_->get_gamma_correct();
|
||||||
float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma);
|
float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma);
|
||||||
float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma);
|
float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma);
|
||||||
float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma);
|
float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma);
|
||||||
float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma);
|
float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma);
|
||||||
float brightness = fmaxf(r, fmaxf(g, b));
|
|
||||||
|
|
||||||
auto call = this->light_state_->make_call();
|
auto call = this->light_state_->make_call();
|
||||||
|
|
||||||
|
float color_brightness = fmaxf(r, fmaxf(g, b));
|
||||||
|
float brightness = fmaxf(color_brightness, w);
|
||||||
|
if (brightness == 0.0f) {
|
||||||
|
call.set_state(false);
|
||||||
|
} else {
|
||||||
|
color_brightness /= brightness;
|
||||||
|
w /= brightness;
|
||||||
|
|
||||||
call.set_state(true);
|
call.set_state(true);
|
||||||
call.set_brightness_if_supported(1.0f);
|
call.set_color_mode_if_supported(this->color_mode_);
|
||||||
call.set_color_brightness_if_supported(brightness);
|
call.set_brightness_if_supported(brightness);
|
||||||
|
call.set_color_brightness_if_supported(color_brightness);
|
||||||
call.set_red_if_supported(r);
|
call.set_red_if_supported(r);
|
||||||
call.set_green_if_supported(g);
|
call.set_green_if_supported(g);
|
||||||
call.set_blue_if_supported(b);
|
call.set_blue_if_supported(b);
|
||||||
call.set_white_if_supported(w);
|
call.set_white_if_supported(w);
|
||||||
|
call.set_warm_white_if_supported(w);
|
||||||
|
call.set_cold_white_if_supported(w);
|
||||||
|
}
|
||||||
call.set_transition_length_if_supported(0);
|
call.set_transition_length_if_supported(0);
|
||||||
call.set_publish(false);
|
call.set_publish(false);
|
||||||
call.set_save(false);
|
call.set_save(false);
|
||||||
@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight {
|
|||||||
|
|
||||||
light::LightState *light_state_;
|
light::LightState *light_state_;
|
||||||
uint8_t *wrapper_state_;
|
uint8_t *wrapper_state_;
|
||||||
|
ColorMode color_mode_{ColorMode::UNKNOWN};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -39,7 +39,15 @@ class LightTransformer {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// The progress of this transition, on a scale of 0 to 1.
|
/// The progress of this transition, on a scale of 0 to 1.
|
||||||
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
|
float get_progress_() {
|
||||||
|
uint32_t now = esphome::millis();
|
||||||
|
if (now < this->start_time_)
|
||||||
|
return 0.0f;
|
||||||
|
if (now >= this->start_time_ + this->length_)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t start_time_;
|
uint32_t start_time_;
|
||||||
uint32_t length_;
|
uint32_t length_;
|
||||||
|
@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer {
|
|||||||
this->start_values_.set_brightness(0.0f);
|
this->start_values_.set_brightness(0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When turning light off from on state, use source state and only decrease brightness to zero.
|
// When turning light off from on state, use source state and only decrease brightness to zero. Use a second
|
||||||
|
// variable for transition end state, as overwriting target_values breaks LightState logic.
|
||||||
if (this->start_values_.is_on() && !this->target_values_.is_on()) {
|
if (this->start_values_.is_on() && !this->target_values_.is_on()) {
|
||||||
this->target_values_ = LightColorValues(this->start_values_);
|
this->end_values_ = LightColorValues(this->start_values_);
|
||||||
this->target_values_.set_brightness(0.0f);
|
this->end_values_.set_brightness(0.0f);
|
||||||
|
} else {
|
||||||
|
this->end_values_ = LightColorValues(this->target_values_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||||
@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
||||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
|
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_;
|
||||||
if (this->changing_color_mode_)
|
if (this->changing_color_mode_)
|
||||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||||
|
|
||||||
@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer {
|
|||||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||||
|
|
||||||
bool changing_color_mode_{false};
|
bool changing_color_mode_{false};
|
||||||
|
LightColorValues end_values_{};
|
||||||
LightColorValues intermediate_values_{};
|
LightColorValues intermediate_values_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,9 +73,7 @@ class LightFlashTransformer : public LightTransformer {
|
|||||||
if (this->transition_length_ * 2 > this->length_)
|
if (this->transition_length_ * 2 > this->length_)
|
||||||
this->transition_length_ = this->length_ / 2;
|
this->transition_length_ = this->length_ / 2;
|
||||||
|
|
||||||
// do not create transition if length is 0
|
this->begun_lightstate_restore_ = false;
|
||||||
if (this->transition_length_ == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// first transition to original target
|
// first transition to original target
|
||||||
this->transformer_ = this->state_.get_output()->create_default_transition();
|
this->transformer_ = this->state_.get_output()->create_default_transition();
|
||||||
@ -79,40 +81,45 @@ class LightFlashTransformer : public LightTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
optional<LightColorValues> apply() override {
|
optional<LightColorValues> apply() override {
|
||||||
// transition transformer does not handle 0 length as progress returns nan
|
optional<LightColorValues> result = {};
|
||||||
if (this->transition_length_ == 0)
|
|
||||||
return this->target_values_;
|
if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) {
|
||||||
|
// second transition back to start value
|
||||||
|
this->transformer_ = this->state_.get_output()->create_default_transition();
|
||||||
|
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
|
||||||
|
this->begun_lightstate_restore_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this->transformer_ != nullptr) {
|
if (this->transformer_ != nullptr) {
|
||||||
if (!this->transformer_->is_finished()) {
|
result = this->transformer_->apply();
|
||||||
return this->transformer_->apply();
|
|
||||||
} else {
|
if (this->transformer_->is_finished()) {
|
||||||
this->transformer_->stop();
|
this->transformer_->stop();
|
||||||
this->transformer_ = nullptr;
|
this->transformer_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (millis() > this->start_time_ + this->length_ - this->transition_length_) {
|
return result;
|
||||||
// second transition back to start value
|
|
||||||
this->transformer_ = this->state_.get_output()->create_default_transition();
|
|
||||||
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// once transition is complete, don't change states until next transition
|
|
||||||
return optional<LightColorValues>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the original values after the flash.
|
// Restore the original values after the flash.
|
||||||
void stop() override {
|
void stop() override {
|
||||||
|
if (this->transformer_ != nullptr) {
|
||||||
|
this->transformer_->stop();
|
||||||
|
this->transformer_ = nullptr;
|
||||||
|
}
|
||||||
this->state_.current_values = this->get_start_values();
|
this->state_.current_values = this->get_start_values();
|
||||||
this->state_.remote_values = this->get_start_values();
|
this->state_.remote_values = this->get_start_values();
|
||||||
this->state_.publish_state();
|
this->state_.publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LightState &state_;
|
LightState &state_;
|
||||||
uint32_t transition_length_;
|
uint32_t transition_length_;
|
||||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||||
|
bool begun_lightstate_restore_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -19,6 +19,7 @@ from esphome.const import (
|
|||||||
CONF_TX_BUFFER_SIZE,
|
CONF_TX_BUFFER_SIZE,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
|
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
|
||||||
|
from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
logger_ns = cg.esphome_ns.namespace("logger")
|
logger_ns = cg.esphome_ns.namespace("logger")
|
||||||
@ -52,6 +53,10 @@ LOG_LEVEL_SEVERITY = [
|
|||||||
"VERY_VERBOSE",
|
"VERY_VERBOSE",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2]
|
||||||
|
|
||||||
|
UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"]
|
||||||
|
|
||||||
UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"]
|
UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"]
|
||||||
|
|
||||||
UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"]
|
UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"]
|
||||||
@ -75,6 +80,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
|||||||
|
|
||||||
def uart_selection(value):
|
def uart_selection(value):
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
if get_esp32_variant() in ESP32_REDUCED_VARIANTS:
|
||||||
|
return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value)
|
||||||
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
|
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||||
|
@ -153,13 +153,9 @@ void Logger::pre_setup() {
|
|||||||
case UART_SELECTION_UART1:
|
case UART_SELECTION_UART1:
|
||||||
this->hw_serial_ = &Serial1;
|
this->hw_serial_ = &Serial1;
|
||||||
break;
|
break;
|
||||||
#ifdef USE_ESP32
|
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||||
case UART_SELECTION_UART2:
|
case UART_SELECTION_UART2:
|
||||||
#if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3
|
|
||||||
// FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has
|
|
||||||
// UART0-UART1)
|
|
||||||
this->hw_serial_ = &Serial2;
|
this->hw_serial_ = &Serial2;
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -173,9 +169,11 @@ void Logger::pre_setup() {
|
|||||||
case UART_SELECTION_UART1:
|
case UART_SELECTION_UART1:
|
||||||
uart_num_ = UART_NUM_1;
|
uart_num_ = UART_NUM_1;
|
||||||
break;
|
break;
|
||||||
|
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||||
case UART_SELECTION_UART2:
|
case UART_SELECTION_UART2:
|
||||||
uart_num_ = UART_NUM_2;
|
uart_num_ = UART_NUM_2;
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
uart_config_t uart_config{};
|
uart_config_t uart_config{};
|
||||||
uart_config.baud_rate = (int) baud_rate_;
|
uart_config.baud_rate = (int) baud_rate_;
|
||||||
|
@ -24,7 +24,7 @@ namespace logger {
|
|||||||
enum UARTSelection {
|
enum UARTSelection {
|
||||||
UART_SELECTION_UART0 = 0,
|
UART_SELECTION_UART0 = 0,
|
||||||
UART_SELECTION_UART1,
|
UART_SELECTION_UART1,
|
||||||
#ifdef USE_ESP32
|
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||||
UART_SELECTION_UART2,
|
UART_SELECTION_UART2,
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "sgp30.h"
|
#include "sgp30.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2021.10.0b2"
|
__version__ = "2021.10.0b3"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user