mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	TCS34725 BugFix and GA factor (#2445)
- Fixed endianness bug on tcs34725 data read - Fixed lux adjustments based on gain, integration time and GA factor - Added glass attenuation factor to allow using this sensor behind semi transparent glass Co-authored-by: Razorback16 <razorback16@users.noreply.github.com>
This commit is contained in:
		| @@ -6,6 +6,7 @@ from esphome.const import ( | |||||||
|     CONF_GAIN, |     CONF_GAIN, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_ILLUMINANCE, |     CONF_ILLUMINANCE, | ||||||
|  |     CONF_GLASS_ATTENUATION_FACTOR, | ||||||
|     CONF_INTEGRATION_TIME, |     CONF_INTEGRATION_TIME, | ||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|     ICON_LIGHTBULB, |     ICON_LIGHTBULB, | ||||||
| @@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = { | |||||||
|     "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, |     "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, | ||||||
|     "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, |     "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, | ||||||
|     "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, |     "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, | ||||||
|  |     "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS, | ||||||
|     "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, |     "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, | ||||||
|     "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, |     "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS, | ||||||
|  |     "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS, | ||||||
|  |     "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS, | ||||||
|  |     "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS, | ||||||
|  |     "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS, | ||||||
|  |     "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS, | ||||||
|  |     "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS, | ||||||
|  |     "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS, | ||||||
|  |     "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS, | ||||||
|  |     "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS, | ||||||
|  |     "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS, | ||||||
|  |     "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS, | ||||||
| } | } | ||||||
|  |  | ||||||
| TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") | TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") | ||||||
| @@ -79,6 +92,9 @@ CONFIG_SCHEMA = ( | |||||||
|                 TCS34725_INTEGRATION_TIMES, lower=True |                 TCS34725_INTEGRATION_TIMES, lower=True | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), |             cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), | ||||||
|  |             cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( | ||||||
|  |                 min=1.0 | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -93,6 +109,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) |     cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) | ||||||
|     cg.add(var.set_gain(config[CONF_GAIN])) |     cg.add(var.set_gain(config[CONF_GAIN])) | ||||||
|  |     cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) | ||||||
|  |  | ||||||
|     if CONF_RED_CHANNEL in config: |     if CONF_RED_CHANNEL in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) |         sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) | ||||||
|   | |||||||
| @@ -26,10 +26,8 @@ void TCS34725Component::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint8_t integration_reg = this->integration_time_; |   if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || | ||||||
|   uint8_t gain_reg = this->gain_; |       !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { | ||||||
|   if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) || |  | ||||||
|       !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) { |  | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -61,6 +59,114 @@ void TCS34725Component::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Color Temperature", this->color_temperature_sensor_); |   LOG_SENSOR("  ", "Color Temperature", this->color_temperature_sensor_); | ||||||
| } | } | ||||||
| float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } | float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | /*! | ||||||
|  |  *  @brief  Converts the raw R/G/B values to color temperature in degrees | ||||||
|  |  *          Kelvin using the algorithm described in DN40 from Taos (now AMS). | ||||||
|  |  *  @param  r | ||||||
|  |  *          Red value | ||||||
|  |  *  @param  g | ||||||
|  |  *          Green value | ||||||
|  |  *  @param  b | ||||||
|  |  *          Blue value | ||||||
|  |  *  @param  c | ||||||
|  |  *          Clear channel value | ||||||
|  |  *  @return Color temperature in degrees Kelvin | ||||||
|  |  */ | ||||||
|  | void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { | ||||||
|  |   float r2, g2, b2; /* RGB values minus IR component */ | ||||||
|  |   float sat;        /* Digital saturation level */ | ||||||
|  |   float ir;         /* Inferred IR content */ | ||||||
|  |  | ||||||
|  |   this->illuminance_ = 0;  // Assign 0 value before calculation | ||||||
|  |   this->color_temperature_ = 0; | ||||||
|  |  | ||||||
|  |   const float ga = this->glass_attenuation_;  // Glass Attenuation Factor | ||||||
|  |   static const float DF = 310.f;              // Device Factor | ||||||
|  |   static const float R_COEF = 0.136f;         // | ||||||
|  |   static const float G_COEF = 1.f;            // used in lux computation | ||||||
|  |   static const float B_COEF = -0.444f;        // | ||||||
|  |   static const float CT_COEF = 3810.f;        // Color Temperature Coefficient | ||||||
|  |   static const float CT_OFFSET = 1391.f;      // Color Temperatuer Offset | ||||||
|  |  | ||||||
|  |   if (c == 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* Analog/Digital saturation: | ||||||
|  |    * | ||||||
|  |    * (a) As light becomes brighter, the clear channel will tend to | ||||||
|  |    *     saturate first since R+G+B is approximately equal to C. | ||||||
|  |    * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration | ||||||
|  |    *     time, up to a maximum values of 65535. This means analog | ||||||
|  |    *     saturation can occur up to an integration time of 153.6ms | ||||||
|  |    *     (64*2.4ms=153.6ms). | ||||||
|  |    * (c) If the integration time is > 153.6ms, digital saturation will | ||||||
|  |    *     occur before analog saturation. Digital saturation occurs when | ||||||
|  |    *     the count reaches 65535. | ||||||
|  |    */ | ||||||
|  |   if ((256 - this->integration_reg_) > 63) { | ||||||
|  |     /* Track digital saturation */ | ||||||
|  |     sat = 65535.f; | ||||||
|  |   } else { | ||||||
|  |     /* Track analog saturation */ | ||||||
|  |     sat = 1024.f * (256.f - this->integration_reg_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* Ripple rejection: | ||||||
|  |    * | ||||||
|  |    * (a) An integration time of 50ms or multiples of 50ms are required to | ||||||
|  |    *     reject both 50Hz and 60Hz ripple. | ||||||
|  |    * (b) If an integration time faster than 50ms is required, you may need | ||||||
|  |    *     to average a number of samples over a 50ms period to reject ripple | ||||||
|  |    *     from fluorescent and incandescent light sources. | ||||||
|  |    * | ||||||
|  |    * Ripple saturation notes: | ||||||
|  |    * | ||||||
|  |    * (a) If there is ripple in the received signal, the value read from C | ||||||
|  |    *     will be less than the max, but still have some effects of being | ||||||
|  |    *     saturated. This means that you can be below the 'sat' value, but | ||||||
|  |    *     still be saturating. At integration times >150ms this can be | ||||||
|  |    *     ignored, but <= 150ms you should calculate the 75% saturation | ||||||
|  |    *     level to avoid this problem. | ||||||
|  |    */ | ||||||
|  |   if (this->integration_time_ < 150) { | ||||||
|  |     /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ | ||||||
|  |     sat -= sat / 4.f; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* Check for saturation and mark the sample as invalid if true */ | ||||||
|  |   if (c >= sat) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* AMS RGB sensors have no IR channel, so the IR content must be */ | ||||||
|  |   /* calculated indirectly. */ | ||||||
|  |   ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0; | ||||||
|  |  | ||||||
|  |   /* Remove the IR component from the raw RGB values */ | ||||||
|  |   r2 = r - ir; | ||||||
|  |   g2 = g - ir; | ||||||
|  |   b2 = b - ir; | ||||||
|  |  | ||||||
|  |   if (r2 == 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Lux Calculation (DN40 3.2) | ||||||
|  |  | ||||||
|  |   float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; | ||||||
|  |   float cpl = (this->integration_time_ * this->gain_) / (ga * DF); | ||||||
|  |   this->illuminance_ = g1 / cpl; | ||||||
|  |  | ||||||
|  |   // Color Temperature Calculation (DN40) | ||||||
|  |   /* A simple method of measuring color temp is to use the ratio of blue */ | ||||||
|  |   /* to red light, taking IR cancellation into account. */ | ||||||
|  |   this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */ | ||||||
|  |                                  r2 + | ||||||
|  |                              CT_OFFSET; /** Color temp offset. */ | ||||||
|  | } | ||||||
|  |  | ||||||
| void TCS34725Component::update() { | void TCS34725Component::update() { | ||||||
|   uint16_t raw_c; |   uint16_t raw_c; | ||||||
|   uint16_t raw_r; |   uint16_t raw_r; | ||||||
| @@ -74,6 +180,12 @@ void TCS34725Component::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian | ||||||
|  |   raw_c = i2c::i2ctohs(raw_c); | ||||||
|  |   raw_r = i2c::i2ctohs(raw_r); | ||||||
|  |   raw_g = i2c::i2ctohs(raw_g); | ||||||
|  |   raw_b = i2c::i2ctohs(raw_b); | ||||||
|  |  | ||||||
|   const float channel_c = raw_c / 655.35f; |   const float channel_c = raw_c / 655.35f; | ||||||
|   const float channel_r = raw_r / 655.35f; |   const float channel_r = raw_r / 655.35f; | ||||||
|   const float channel_g = raw_g / 655.35f; |   const float channel_g = raw_g / 655.35f; | ||||||
| @@ -87,38 +199,54 @@ void TCS34725Component::update() { | |||||||
|   if (this->blue_sensor_ != nullptr) |   if (this->blue_sensor_ != nullptr) | ||||||
|     this->blue_sensor_->publish_state(channel_b); |     this->blue_sensor_->publish_state(channel_b); | ||||||
|  |  | ||||||
|   // Formulae taken from Adafruit TCS35725 library |   if (this->illuminance_sensor_ || this->color_temperature_sensor_) { | ||||||
|   float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b); |     calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (this->illuminance_sensor_ != nullptr) |   if (this->illuminance_sensor_ != nullptr) | ||||||
|     this->illuminance_sensor_->publish_state(illuminance); |     this->illuminance_sensor_->publish_state(this->illuminance_); | ||||||
|  |  | ||||||
|   // Color temperature |  | ||||||
|   // 1. Convert RGB to XYZ color space |  | ||||||
|   const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b); |  | ||||||
|   const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b); |  | ||||||
|   const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b); |  | ||||||
|  |  | ||||||
|   // 2. Calculate chromacity coordinates |  | ||||||
|   const float xc = (x) / (x + y + z); |  | ||||||
|   const float yc = (y) / (x + y + z); |  | ||||||
|  |  | ||||||
|   // 3. Use McCamy's formula to determine the color temperature |  | ||||||
|   const float n = (xc - 0.3320f) / (0.1858f - yc); |  | ||||||
|  |  | ||||||
|   // 4. final color temperature in Kelvin. |  | ||||||
|   const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f; |  | ||||||
|   if (this->color_temperature_sensor_ != nullptr) |   if (this->color_temperature_sensor_ != nullptr) | ||||||
|     this->color_temperature_sensor_->publish_state(color_temperature); |     this->color_temperature_sensor_->publish_state(this->color_temperature_); | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, |   ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, | ||||||
|            channel_g, channel_b, channel_c, illuminance, color_temperature); |            channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); | ||||||
|  |  | ||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
| void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { | void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { | ||||||
|   this->integration_time_ = integration_time; |   this->integration_reg_ = integration_time; | ||||||
|  |   this->integration_time_ = (256.f - integration_time) * 2.4f; | ||||||
|  | } | ||||||
|  | void TCS34725Component::set_gain(TCS34725Gain gain) { | ||||||
|  |   this->gain_reg_ = gain; | ||||||
|  |   switch (gain) { | ||||||
|  |     case TCS34725Gain::TCS34725_GAIN_1X: | ||||||
|  |       this->gain_ = 1.f; | ||||||
|  |       break; | ||||||
|  |     case TCS34725Gain::TCS34725_GAIN_4X: | ||||||
|  |       this->gain_ = 4.f; | ||||||
|  |       break; | ||||||
|  |     case TCS34725Gain::TCS34725_GAIN_16X: | ||||||
|  |       this->gain_ = 16.f; | ||||||
|  |       break; | ||||||
|  |     case TCS34725Gain::TCS34725_GAIN_60X: | ||||||
|  |       this->gain_ = 60.f; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       this->gain_ = 1.f; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TCS34725Component::set_glass_attenuation_factor(float ga) { | ||||||
|  |   // The Glass Attenuation (FA) factor used to compensate for lower light | ||||||
|  |   // levels at the device due to the possible presence of glass. The GA is | ||||||
|  |   // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity | ||||||
|  |   // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1. | ||||||
|  |   // See Application Note: DN40-Rev 1.0 | ||||||
|  |   this->glass_attenuation_ = ga; | ||||||
| } | } | ||||||
| void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; } |  | ||||||
|  |  | ||||||
| }  // namespace tcs34725 | }  // namespace tcs34725 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -12,8 +12,20 @@ enum TCS34725IntegrationTime { | |||||||
|   TCS34725_INTEGRATION_TIME_24MS = 0xF6, |   TCS34725_INTEGRATION_TIME_24MS = 0xF6, | ||||||
|   TCS34725_INTEGRATION_TIME_50MS = 0xEB, |   TCS34725_INTEGRATION_TIME_50MS = 0xEB, | ||||||
|   TCS34725_INTEGRATION_TIME_101MS = 0xD5, |   TCS34725_INTEGRATION_TIME_101MS = 0xD5, | ||||||
|  |   TCS34725_INTEGRATION_TIME_120MS = 0xCE, | ||||||
|   TCS34725_INTEGRATION_TIME_154MS = 0xC0, |   TCS34725_INTEGRATION_TIME_154MS = 0xC0, | ||||||
|   TCS34725_INTEGRATION_TIME_700MS = 0x00, |   TCS34725_INTEGRATION_TIME_180MS = 0xB5, | ||||||
|  |   TCS34725_INTEGRATION_TIME_199MS = 0xAD, | ||||||
|  |   TCS34725_INTEGRATION_TIME_240MS = 0x9C, | ||||||
|  |   TCS34725_INTEGRATION_TIME_300MS = 0x83, | ||||||
|  |   TCS34725_INTEGRATION_TIME_360MS = 0x6A, | ||||||
|  |   TCS34725_INTEGRATION_TIME_401MS = 0x59, | ||||||
|  |   TCS34725_INTEGRATION_TIME_420MS = 0x51, | ||||||
|  |   TCS34725_INTEGRATION_TIME_480MS = 0x38, | ||||||
|  |   TCS34725_INTEGRATION_TIME_499MS = 0x30, | ||||||
|  |   TCS34725_INTEGRATION_TIME_540MS = 0x1F, | ||||||
|  |   TCS34725_INTEGRATION_TIME_600MS = 0x06, | ||||||
|  |   TCS34725_INTEGRATION_TIME_614MS = 0x00, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum TCS34725Gain { | enum TCS34725Gain { | ||||||
| @@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  public: |  public: | ||||||
|   void set_integration_time(TCS34725IntegrationTime integration_time); |   void set_integration_time(TCS34725IntegrationTime integration_time); | ||||||
|   void set_gain(TCS34725Gain gain); |   void set_gain(TCS34725Gain gain); | ||||||
|  |   void set_glass_attenuation_factor(float ga); | ||||||
|  |  | ||||||
|   void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } |   void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } | ||||||
|   void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } |   void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } | ||||||
| @@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   sensor::Sensor *blue_sensor_{nullptr}; |   sensor::Sensor *blue_sensor_{nullptr}; | ||||||
|   sensor::Sensor *illuminance_sensor_{nullptr}; |   sensor::Sensor *illuminance_sensor_{nullptr}; | ||||||
|   sensor::Sensor *color_temperature_sensor_{nullptr}; |   sensor::Sensor *color_temperature_sensor_{nullptr}; | ||||||
|   TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS}; |   float integration_time_{2.4}; | ||||||
|   TCS34725Gain gain_{TCS34725_GAIN_1X}; |   float gain_{1.0}; | ||||||
|  |   float glass_attenuation_{1.0}; | ||||||
|  |   float illuminance_; | ||||||
|  |   float color_temperature_; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c); | ||||||
|  |   uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS}; | ||||||
|  |   uint8_t gain_reg_{TCS34725_GAIN_1X}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace tcs34725 | }  // namespace tcs34725 | ||||||
|   | |||||||
| @@ -403,7 +403,7 @@ sensor: | |||||||
|       name: Illuminance |       name: Illuminance | ||||||
|     color_temperature: |     color_temperature: | ||||||
|       name: Color Temperature |       name: Color Temperature | ||||||
|     integration_time: 700ms |     integration_time: 614ms | ||||||
|     gain: 60x |     gain: 60x | ||||||
|   - platform: custom |   - platform: custom | ||||||
|     lambda: |- |     lambda: |- | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user