mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[pi4ioe5v6408] Add new IO Expander (#8888)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
		| @@ -332,6 +332,7 @@ esphome/components/pca6416a/* @Mat931 | |||||||
| esphome/components/pca9554/* @clydebarrow @hwstar | esphome/components/pca9554/* @clydebarrow @hwstar | ||||||
| esphome/components/pcf85063/* @brogon | esphome/components/pcf85063/* @brogon | ||||||
| esphome/components/pcf8563/* @KoenBreeman | esphome/components/pcf8563/* @KoenBreeman | ||||||
|  | esphome/components/pi4ioe5v6408/* @jesserockz | ||||||
| esphome/components/pid/* @OttoWinter | esphome/components/pid/* @OttoWinter | ||||||
| esphome/components/pipsolar/* @andreashergert1984 | esphome/components/pipsolar/* @andreashergert1984 | ||||||
| esphome/components/pm1006/* @habbie | esphome/components/pm1006/* @habbie | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								esphome/components/pi4ioe5v6408/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								esphome/components/pi4ioe5v6408/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INPUT, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_MODE, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     CONF_OUTPUT, | ||||||
|  |     CONF_PULLDOWN, | ||||||
|  |     CONF_PULLUP, | ||||||
|  |     CONF_RESET, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["gpio_expander"] | ||||||
|  | CODEOWNERS = ["@jesserockz"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | pi4ioe5v6408_ns = cg.esphome_ns.namespace("pi4ioe5v6408") | ||||||
|  | PI4IOE5V6408Component = pi4ioe5v6408_ns.class_( | ||||||
|  |     "PI4IOE5V6408Component", cg.Component, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  | PI4IOE5V6408GPIOPin = pi4ioe5v6408_ns.class_("PI4IOE5V6408GPIOPin", cg.GPIOPin) | ||||||
|  |  | ||||||
|  | CONF_PI4IOE5V6408 = "pi4ioe5v6408" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(PI4IOE5V6408Component), | ||||||
|  |             cv.Optional(CONF_RESET, default=True): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x43)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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_reset(config[CONF_RESET])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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") | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( | ||||||
|  |     PI4IOE5V6408GPIOPin, | ||||||
|  |     cv.int_range(min=0, max=7), | ||||||
|  |     modes=[ | ||||||
|  |         CONF_INPUT, | ||||||
|  |         CONF_OUTPUT, | ||||||
|  |         CONF_PULLUP, | ||||||
|  |         CONF_PULLDOWN, | ||||||
|  |     ], | ||||||
|  |     mode_validator=validate_mode, | ||||||
|  | ).extend( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_PI4IOE5V6408): cv.use_id(PI4IOE5V6408Component), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pins.PIN_SCHEMA_REGISTRY.register(CONF_PI4IOE5V6408, PI4IOE5V6408_PIN_SCHEMA) | ||||||
|  | async def pi4ioe5v6408_pin_schema(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_parented(var, config[CONF_PI4IOE5V6408]) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_pin(config[CONF_NUMBER])) | ||||||
|  |     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||||
|  |     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||||
|  |     return var | ||||||
							
								
								
									
										171
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | |||||||
|  | #include "pi4ioe5v6408.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pi4ioe5v6408 { | ||||||
|  |  | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_DEVICE_ID = 0x01; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_IO_DIR = 0x03; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_OUT_SET = 0x05; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_OUT_HIGH_IMPEDENCE = 0x07; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_IN_DEFAULT_STATE = 0x09; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_PULL_ENABLE = 0x0B; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_PULL_SELECT = 0x0D; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_IN_STATE = 0x0F; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_ENABLE_MASK = 0x11; | ||||||
|  | static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_STATUS = 0x13; | ||||||
|  |  | ||||||
|  | static const char *const TAG = "pi4ioe5v6408"; | ||||||
|  |  | ||||||
|  | void PI4IOE5V6408Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
|  |   if (this->reset_) { | ||||||
|  |     this->reg(PI4IOE5V6408_REGISTER_DEVICE_ID) |= 0b00000001; | ||||||
|  |     this->reg(PI4IOE5V6408_REGISTER_OUT_HIGH_IMPEDENCE) = 0b00000000; | ||||||
|  |   } else { | ||||||
|  |     if (!this->read_gpio_modes_()) { | ||||||
|  |       this->mark_failed(); | ||||||
|  |       ESP_LOGE(TAG, "Failed to read GPIO modes"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (!this->read_gpio_outputs_()) { | ||||||
|  |       this->mark_failed(); | ||||||
|  |       ESP_LOGE(TAG, "Failed to read GPIO outputs"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void PI4IOE5V6408Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "PI4IOE5V6408:"); | ||||||
|  |   LOG_I2C_DEVICE(this) | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void PI4IOE5V6408Component::pin_mode(uint8_t pin, gpio::Flags flags) { | ||||||
|  |   if (flags & gpio::FLAG_OUTPUT) { | ||||||
|  |     // Set mode mask bit | ||||||
|  |     this->mode_mask_ |= 1 << pin; | ||||||
|  |   } else if (flags & gpio::FLAG_INPUT) { | ||||||
|  |     // Clear mode mask bit | ||||||
|  |     this->mode_mask_ &= ~(1 << pin); | ||||||
|  |     if (flags & gpio::FLAG_PULLUP) { | ||||||
|  |       this->pull_up_down_mask_ |= 1 << pin; | ||||||
|  |       this->pull_enable_mask_ |= 1 << pin; | ||||||
|  |     } else if (flags & gpio::FLAG_PULLDOWN) { | ||||||
|  |       this->pull_up_down_mask_ &= ~(1 << pin); | ||||||
|  |       this->pull_enable_mask_ |= 1 << pin; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Write GPIO to enable input mode | ||||||
|  |   this->write_gpio_modes_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PI4IOE5V6408Component::loop() { this->reset_pin_cache_(); } | ||||||
|  |  | ||||||
|  | bool PI4IOE5V6408Component::read_gpio_outputs_() { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   uint8_t data; | ||||||
|  |   if (!this->read_byte(PI4IOE5V6408_REGISTER_OUT_SET, &data)) { | ||||||
|  |     this->status_set_warning("Failed to read output register"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   this->output_mask_ = data; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool PI4IOE5V6408Component::read_gpio_modes_() { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   uint8_t data; | ||||||
|  |   if (!this->read_byte(PI4IOE5V6408_REGISTER_IO_DIR, &data)) { | ||||||
|  |     this->status_set_warning("Failed to read GPIO modes"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|  |   ESP_LOGV(TAG, "Read GPIO modes: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(data)); | ||||||
|  | #endif | ||||||
|  |   this->mode_mask_ = data; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool PI4IOE5V6408Component::digital_read_hw(uint8_t pin) { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   uint8_t data; | ||||||
|  |   if (!this->read_byte(PI4IOE5V6408_REGISTER_IN_STATE, &data)) { | ||||||
|  |     this->status_set_warning("Failed to read GPIO state"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   this->input_mask_ = data; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PI4IOE5V6408Component::digital_write_hw(uint8_t pin, bool value) { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   if (value) { | ||||||
|  |     this->output_mask_ |= (1 << pin); | ||||||
|  |   } else { | ||||||
|  |     this->output_mask_ &= ~(1 << pin); | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(PI4IOE5V6408_REGISTER_OUT_SET, this->output_mask_)) { | ||||||
|  |     this->status_set_warning("Failed to write output register"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|  |   ESP_LOGV(TAG, "Wrote GPIO output: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(this->output_mask_)); | ||||||
|  | #endif | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool PI4IOE5V6408Component::write_gpio_modes_() { | ||||||
|  |   if (this->is_failed()) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   if (!this->write_byte(PI4IOE5V6408_REGISTER_IO_DIR, this->mode_mask_)) { | ||||||
|  |     this->status_set_warning("Failed to write GPIO modes"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_SELECT, this->pull_up_down_mask_)) { | ||||||
|  |     this->status_set_warning("Failed to write GPIO pullup/pulldown"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_ENABLE, this->pull_enable_mask_)) { | ||||||
|  |     this->status_set_warning("Failed to write GPIO pull enable"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|  |   ESP_LOGV(TAG, | ||||||
|  |            "Wrote GPIO modes: 0b" BYTE_TO_BINARY_PATTERN "\n" | ||||||
|  |            "Wrote GPIO pullup/pulldown: 0b" BYTE_TO_BINARY_PATTERN "\n" | ||||||
|  |            "Wrote GPIO pull enable: 0b" BYTE_TO_BINARY_PATTERN, | ||||||
|  |            BYTE_TO_BINARY(this->mode_mask_), BYTE_TO_BINARY(this->pull_up_down_mask_), | ||||||
|  |            BYTE_TO_BINARY(this->pull_enable_mask_)); | ||||||
|  | #endif | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool PI4IOE5V6408Component::digital_read_cache(uint8_t pin) { return (this->input_mask_ & (1 << pin)); } | ||||||
|  |  | ||||||
|  | float PI4IOE5V6408Component::get_setup_priority() const { return setup_priority::IO; } | ||||||
|  |  | ||||||
|  | void PI4IOE5V6408GPIOPin::setup() { this->pin_mode(this->flags_); } | ||||||
|  | void PI4IOE5V6408GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||||
|  | bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||||
|  | void PI4IOE5V6408GPIOPin::digital_write(bool value) { | ||||||
|  |   this->parent_->digital_write(this->pin_, value != this->inverted_); | ||||||
|  | } | ||||||
|  | std::string PI4IOE5V6408GPIOPin::dump_summary() const { return str_sprintf("%u via PI4IOE5V6408", this->pin_); } | ||||||
|  |  | ||||||
|  | }  // namespace pi4ioe5v6408 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										70
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/gpio_expander/cached_gpio.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace pi4ioe5v6408 { | ||||||
|  | class PI4IOE5V6408Component : public Component, | ||||||
|  |                               public i2c::I2CDevice, | ||||||
|  |                               public gpio_expander::CachedGpioExpander<uint8_t, 8> { | ||||||
|  |  public: | ||||||
|  |   PI4IOE5V6408Component() = default; | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void pin_mode(uint8_t pin, gpio::Flags flags); | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   /// Indicate if the component should reset the state during setup | ||||||
|  |   void set_reset(bool reset) { this->reset_ = reset; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool digital_read_hw(uint8_t pin) override; | ||||||
|  |   bool digital_read_cache(uint8_t pin) override; | ||||||
|  |   void digital_write_hw(uint8_t pin, bool value) override; | ||||||
|  |  | ||||||
|  |   /// Mask for the pin mode - 1 means output, 0 means input | ||||||
|  |   uint8_t mode_mask_{0x00}; | ||||||
|  |   /// The mask to write as output state - 1 means HIGH, 0 means LOW | ||||||
|  |   uint8_t output_mask_{0x00}; | ||||||
|  |   /// The state read in digital_read_hw - 1 means HIGH, 0 means LOW | ||||||
|  |   uint8_t input_mask_{0x00}; | ||||||
|  |   /// The mask to write as input buffer state - 1 means enabled, 0 means disabled | ||||||
|  |   uint8_t pull_enable_mask_{0x00}; | ||||||
|  |   /// The mask to write as pullup state - 1 means pullup, 0 means pulldown | ||||||
|  |   uint8_t pull_up_down_mask_{0x00}; | ||||||
|  |  | ||||||
|  |   bool reset_{true}; | ||||||
|  |  | ||||||
|  |   bool read_gpio_modes_(); | ||||||
|  |   bool write_gpio_modes_(); | ||||||
|  |   bool read_gpio_outputs_(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class PI4IOE5V6408GPIOPin : public GPIOPin, public Parented<PI4IOE5V6408Component> { | ||||||
|  |  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_pin(uint8_t pin) { this->pin_ = pin; } | ||||||
|  |   void set_inverted(bool inverted) { this->inverted_ = inverted; } | ||||||
|  |   void set_flags(gpio::Flags flags) { this->flags_ = flags; } | ||||||
|  |  | ||||||
|  |   gpio::Flags get_flags() const override { return this->flags_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t pin_; | ||||||
|  |   bool inverted_; | ||||||
|  |   gpio::Flags flags_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace pi4ioe5v6408 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										22
									
								
								tests/components/pi4ioe5v6408/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/components/pi4ioe5v6408/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | i2c: | ||||||
|  |   id: i2c_pi4ioe5v6408 | ||||||
|  |   sda: ${i2c_sda} | ||||||
|  |   scl: ${i2c_scl} | ||||||
|  |  | ||||||
|  | pi4ioe5v6408: | ||||||
|  |   id: pi4ioe1 | ||||||
|  |   address: 0x44 | ||||||
|  |  | ||||||
|  | switch: | ||||||
|  |   - platform: gpio | ||||||
|  |     id: switch1 | ||||||
|  |     pin: | ||||||
|  |       pi4ioe5v6408: pi4ioe1 | ||||||
|  |       number: 0 | ||||||
|  |  | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: gpio | ||||||
|  |     id: sensor1 | ||||||
|  |     pin: | ||||||
|  |       pi4ioe5v6408: pi4ioe1 | ||||||
|  |       number: 1 | ||||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   i2c_sda: GPIO21 | ||||||
|  |   i2c_scl: GPIO22 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   i2c_sda: GPIO21 | ||||||
|  |   i2c_scl: GPIO22 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   i2c_sda: GPIO4 | ||||||
|  |   i2c_scl: GPIO5 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user