Finish cleanup.
This commit is contained in:
parent
96b6a8bec9
commit
e2bb406139
@ -1 +1,5 @@
|
|||||||
idf_component_register(SRC_DIRS . INCLUDE_DIRS .)
|
idf_component_register(
|
||||||
|
SRCS main.cpp display.cpp ssd1306.cpp i2c.h time_keeper.h panel.h panel_device.h
|
||||||
|
INCLUDE_DIRS .
|
||||||
|
REQUIRES driver
|
||||||
|
)
|
||||||
|
@ -129,11 +129,9 @@ void Display::lvgl_increase_tick_cb(void *)
|
|||||||
[[noreturn]] void Display::lvgl_port_task(void *)
|
[[noreturn]] void Display::lvgl_port_task(void *)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "Starting LVGL task");
|
ESP_LOGI(TAG, "Starting LVGL task");
|
||||||
for (uint32_t time_till_next_ms = 0; true;) {
|
for (uint32_t time_to_next_ms = 0; true; usleep(1000 * time_to_next_ms)) {
|
||||||
_lock_acquire(&ScopedLock::lv_lock_);
|
ScopedLock lock;
|
||||||
time_till_next_ms = lv_timer_handler();
|
time_to_next_ms = lv_timer_handler();
|
||||||
_lock_release(&ScopedLock::lv_lock_);
|
|
||||||
usleep(1000 * time_till_next_ms);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,19 +13,17 @@
|
|||||||
#define LVGL_TASK_STACK_SIZE (4 * 1024)
|
#define LVGL_TASK_STACK_SIZE (4 * 1024)
|
||||||
#define LVGL_TASK_PRIORITY 2
|
#define LVGL_TASK_PRIORITY 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates lv_display handle and related LVGL operations.
|
||||||
|
* Contains helper methods that wrap basic LVGL operations such as drawing text.
|
||||||
|
* The underlying lv_display can be obtained for manual LVGL operations.
|
||||||
|
* @sa ScopedLock
|
||||||
|
* @sa Display::get()
|
||||||
|
*/
|
||||||
class Display {
|
class Display {
|
||||||
public:
|
public:
|
||||||
//
|
|
||||||
// CONSTRUCTORS
|
|
||||||
|
|
||||||
Display(const Display &) = delete;
|
|
||||||
|
|
||||||
Display(Display &) = delete;
|
|
||||||
|
|
||||||
Display &operator=(Display &) = delete;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Display using an IPanelDevice.
|
* Construct a new Display using an object that implements IPanelDevice.
|
||||||
*
|
*
|
||||||
* @param device An object that implements the IPanelDevice interface.
|
* @param device An object that implements the IPanelDevice interface.
|
||||||
*/
|
*/
|
||||||
@ -33,6 +31,12 @@ public:
|
|||||||
|
|
||||||
~Display() = default;
|
~Display() = default;
|
||||||
|
|
||||||
|
Display(const Display &) = delete;
|
||||||
|
|
||||||
|
Display(Display &) = delete;
|
||||||
|
|
||||||
|
Display &operator=(Display &) = delete;
|
||||||
|
|
||||||
//
|
//
|
||||||
// GETTERS
|
// GETTERS
|
||||||
|
|
||||||
|
@ -5,14 +5,23 @@
|
|||||||
|
|
||||||
#include <driver/i2c_master.h>
|
#include <driver/i2c_master.h>
|
||||||
|
|
||||||
|
// TODO: Refactor tags for-each class.
|
||||||
static const char *TAG = "lcd-panel";
|
static const char *TAG = "lcd-panel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates ESP I2C creation and usage.
|
||||||
|
*/
|
||||||
struct I2C {
|
struct I2C {
|
||||||
|
/**
|
||||||
|
* Construct and initialize an ESP I2C master bus.
|
||||||
|
* An I2C constructor may only be called one time in any application.
|
||||||
|
*
|
||||||
|
* @param sda GPIO pin number for SDA
|
||||||
|
* @param scl GPIO pin number for SCL
|
||||||
|
* @param rst GPIO pin number for RST
|
||||||
|
*/
|
||||||
I2C(gpio_num_t sda, gpio_num_t scl, int rst = -1) :
|
I2C(gpio_num_t sda, gpio_num_t scl, int rst = -1) :
|
||||||
esp_i2c_bus_(nullptr),
|
I2C((i2c_master_bus_config_t) {
|
||||||
rst_num_(rst),
|
|
||||||
esp_bus_config_(
|
|
||||||
(i2c_master_bus_config_t) {
|
|
||||||
.i2c_port = I2C_BUS_PORT,
|
.i2c_port = I2C_BUS_PORT,
|
||||||
.sda_io_num = sda,
|
.sda_io_num = sda,
|
||||||
.scl_io_num = scl,
|
.scl_io_num = scl,
|
||||||
@ -21,22 +30,44 @@ struct I2C {
|
|||||||
.flags {
|
.flags {
|
||||||
.enable_internal_pullup = true,
|
.enable_internal_pullup = true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
rst
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an ESP I2C master bus given a specific ESP I2C configuration.
|
||||||
|
* An I2C constructor may only be called one time in any application.
|
||||||
|
*
|
||||||
|
* @param config ESP I2C master bus configuration.
|
||||||
|
* @param rst GPIO pin number for RST
|
||||||
|
*/
|
||||||
|
explicit I2C(i2c_master_bus_config_t config, int rst = -1) :
|
||||||
|
esp_bus_config_(config),
|
||||||
|
rst_num_(rst)
|
||||||
{
|
{
|
||||||
|
i2c_master_bus_handle_t i2c;
|
||||||
ESP_LOGI(TAG, "Initializing new master I2C bus");
|
ESP_LOGI(TAG, "Initializing new master I2C bus");
|
||||||
ESP_ERROR_CHECK(i2c_new_master_bus(&esp_bus_config_, &esp_i2c_bus_));
|
ESP_ERROR_CHECK(i2c_new_master_bus(&esp_bus_config_, &i2c));
|
||||||
}
|
}
|
||||||
|
|
||||||
~I2C() = default;
|
~I2C() = default;
|
||||||
|
|
||||||
// TODO: Can you use the I2C get_master_bus API in a static method?
|
/**
|
||||||
i2c_master_bus_handle_t esp_i2c_bus_;
|
* ESP I2C master bus handle getter.
|
||||||
|
* This will fail if an I2C instance was never constructed.
|
||||||
|
*/
|
||||||
|
static i2c_master_bus_handle_t get()
|
||||||
|
{
|
||||||
|
i2c_master_bus_handle_t i2c = nullptr;
|
||||||
|
ESP_ERROR_CHECK(i2c_master_get_bus_handle(0, &i2c));
|
||||||
|
return i2c;
|
||||||
|
}
|
||||||
|
|
||||||
int rst_num_;
|
/// ESP I2C master bus configuration used during initialization.
|
||||||
|
|
||||||
private:
|
|
||||||
i2c_master_bus_config_t esp_bus_config_;
|
i2c_master_bus_config_t esp_bus_config_;
|
||||||
|
|
||||||
|
/// RST GPIO pin number.
|
||||||
|
int rst_num_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //I2C_H
|
#endif //I2C_H
|
||||||
|
@ -3,8 +3,19 @@
|
|||||||
|
|
||||||
#include "panel_device.h"
|
#include "panel_device.h"
|
||||||
|
|
||||||
class Panel {
|
/**
|
||||||
public:
|
* Encapsulates esp_lcd_panel handles and operations.
|
||||||
|
* The only exception is esp_lcd_panel_io_i2c_config_t owned by IPanelDevice.
|
||||||
|
* This structure requires details specific to the implementing device.
|
||||||
|
*
|
||||||
|
* Panel is an implementation detail of Display, not meant to be used directly.
|
||||||
|
*/
|
||||||
|
struct Panel {
|
||||||
|
/**
|
||||||
|
* Construct a new Panel using an object that implements IPanelDevice.
|
||||||
|
*
|
||||||
|
* @param device An object that implements the IPanelDevice interface.
|
||||||
|
*/
|
||||||
explicit Panel(IPanelDevice &device) :
|
explicit Panel(IPanelDevice &device) :
|
||||||
device_(&device),
|
device_(&device),
|
||||||
esp_io_(nullptr),
|
esp_io_(nullptr),
|
||||||
@ -31,13 +42,16 @@ public:
|
|||||||
|
|
||||||
~Panel() = default;
|
~Panel() = default;
|
||||||
|
|
||||||
|
/// Pointer to object using known interface for IPanelDevice.
|
||||||
IPanelDevice *device_;
|
IPanelDevice *device_;
|
||||||
|
|
||||||
|
/// ESP LCD panel IO handle.
|
||||||
esp_lcd_panel_io_handle_t esp_io_;
|
esp_lcd_panel_io_handle_t esp_io_;
|
||||||
|
|
||||||
|
/// ESP LCD panel handle.
|
||||||
esp_lcd_panel_handle_t esp_panel_;
|
esp_lcd_panel_handle_t esp_panel_;
|
||||||
|
|
||||||
private:
|
/// ESP LCD panel configuration structure.
|
||||||
esp_lcd_panel_dev_config_t esp_panel_config_;
|
esp_lcd_panel_dev_config_t esp_panel_config_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,10 +10,28 @@
|
|||||||
|
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
|
|
||||||
|
// LVGL reserves 2x4 bytes in the buffer to be used as a palette.
|
||||||
|
// This additional space must be added to the IPanelDevice::buf_size_.
|
||||||
#define LVGL_PALETTE_SIZE 8
|
#define LVGL_PALETTE_SIZE 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates vendor specific ESP LCD pabel initialization logic.
|
||||||
|
* This pure virtual interface can be inherited from for using new LCD devices.
|
||||||
|
* See the SSD1306 example for implementing a PanelDevice for NT35510 or ST7789.
|
||||||
|
*
|
||||||
|
* At this time only I2C is supported.
|
||||||
|
* Classes that inherit from this interface should likely be marked final.
|
||||||
|
*/
|
||||||
class IPanelDevice {
|
class IPanelDevice {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Construct an IPanelDevice.
|
||||||
|
*
|
||||||
|
* @param i2c I2C object. Eventually this will mature to IProtocol or similar.
|
||||||
|
* @param config I2C configuration for this device.
|
||||||
|
* @param height Height of the device screen in pixels.
|
||||||
|
* @param width Width of the device screen in pixels.
|
||||||
|
*/
|
||||||
explicit IPanelDevice(I2C &i2c,
|
explicit IPanelDevice(I2C &i2c,
|
||||||
esp_lcd_panel_io_i2c_config_t config,
|
esp_lcd_panel_io_i2c_config_t config,
|
||||||
int width,
|
int width,
|
||||||
@ -21,6 +39,15 @@ public:
|
|||||||
IPanelDevice(i2c, config, width, height,
|
IPanelDevice(i2c, config, width, height,
|
||||||
width * height / 8 + LVGL_PALETTE_SIZE) { }
|
width * height / 8 + LVGL_PALETTE_SIZE) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an IPanelDevice.
|
||||||
|
*
|
||||||
|
* @param i2c I2C object. Eventually this will mature to IProtocol or similar.
|
||||||
|
* @param config I2C configuration for this device.
|
||||||
|
* @param height Height of the device screen in pixels.
|
||||||
|
* @param width Width of the device screen in pixels.
|
||||||
|
* @param draw_buf_size Size of the draw buffer for this device.
|
||||||
|
*/
|
||||||
explicit IPanelDevice(I2C &i2c,
|
explicit IPanelDevice(I2C &i2c,
|
||||||
esp_lcd_panel_io_i2c_config_t io_config,
|
esp_lcd_panel_io_i2c_config_t io_config,
|
||||||
int width,
|
int width,
|
||||||
@ -30,11 +57,15 @@ public:
|
|||||||
height_(height),
|
height_(height),
|
||||||
rst_num_(i2c.rst_num_),
|
rst_num_(i2c.rst_num_),
|
||||||
lv_buf_size_(draw_buf_size),
|
lv_buf_size_(draw_buf_size),
|
||||||
esp_i2c_bus_(i2c.esp_i2c_bus_),
|
|
||||||
esp_io_config_(io_config) { }
|
esp_io_config_(io_config) { }
|
||||||
|
|
||||||
virtual ~IPanelDevice() = default;
|
virtual ~IPanelDevice() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an LVGL display using the width and height of this device.
|
||||||
|
*
|
||||||
|
* @return Handle to the created LVGL display.
|
||||||
|
*/
|
||||||
[[nodiscard]] lv_display_t *create_display() const
|
[[nodiscard]] lv_display_t *create_display() const
|
||||||
{
|
{
|
||||||
auto display = lv_display_create(width_, height_);
|
auto display = lv_display_create(width_, height_);
|
||||||
@ -42,15 +73,28 @@ public:
|
|||||||
return display;
|
return display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ESP LCD panel IO handle.
|
||||||
|
*
|
||||||
|
* @return The created ESP LCD panel IO handle.
|
||||||
|
*/
|
||||||
[[nodiscard]] esp_lcd_panel_io_handle_t create_io_handle()
|
[[nodiscard]] esp_lcd_panel_io_handle_t create_io_handle()
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "Creating panel IO handle");
|
ESP_LOGI(TAG, "Creating panel IO handle");
|
||||||
esp_lcd_panel_io_handle_t handle = nullptr;
|
esp_lcd_panel_io_handle_t handle = nullptr;
|
||||||
ESP_ERROR_CHECK(
|
ESP_ERROR_CHECK(
|
||||||
esp_lcd_new_panel_io_i2c(esp_i2c_bus_, &esp_io_config_, &handle));
|
esp_lcd_new_panel_io_i2c(I2C::get(), &esp_io_config_, &handle));
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and initialize an ESP panel handle.
|
||||||
|
* IPanelDevice implementors must initialize the panel within init_panel.
|
||||||
|
*
|
||||||
|
* @param config ESP LCD panel configuration.
|
||||||
|
* @param io ESP LCD panel IO handle.
|
||||||
|
* @param [out] panel ESP LCD panel handle output pointer location.
|
||||||
|
*/
|
||||||
void create_panel(esp_lcd_panel_dev_config_t &config,
|
void create_panel(esp_lcd_panel_dev_config_t &config,
|
||||||
esp_lcd_panel_io_handle_t io,
|
esp_lcd_panel_io_handle_t io,
|
||||||
esp_lcd_panel_handle_t &panel)
|
esp_lcd_panel_handle_t &panel)
|
||||||
@ -62,25 +106,43 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Install SSD1306 panel driver");
|
ESP_LOGI(TAG, "Install SSD1306 panel driver");
|
||||||
|
// Call pure virtual method responsible for initializing the panel handle.
|
||||||
init_panel(config, io, panel);
|
init_panel(config, io, panel);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the device specific vendor configuration structure.
|
||||||
|
*
|
||||||
|
* @return Address of vendor configuration structure.
|
||||||
|
* @sa SSD1306::vendor_config
|
||||||
|
*/
|
||||||
virtual void *vendor_config() = 0;
|
virtual void *vendor_config() = 0;
|
||||||
|
|
||||||
|
/// Width of the device screen in pixels.
|
||||||
int32_t width_;
|
int32_t width_;
|
||||||
|
|
||||||
|
/// Height of the device screen in pixels.
|
||||||
int32_t height_;
|
int32_t height_;
|
||||||
|
|
||||||
|
/// RST GPIO pin number.
|
||||||
int rst_num_;
|
int rst_num_;
|
||||||
|
|
||||||
// LVGL reserves 2x4 bytes in the buffer to be used as a palette.
|
/// LVGL draw buffer size for the device.
|
||||||
size_t lv_buf_size_;
|
size_t lv_buf_size_;
|
||||||
|
|
||||||
i2c_master_bus_handle_t esp_i2c_bus_;
|
/// ESP LCD panel IO configuration.
|
||||||
|
|
||||||
esp_lcd_panel_io_i2c_config_t esp_io_config_;
|
esp_lcd_panel_io_i2c_config_t esp_io_config_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Initializes the ESP panel using vendor specific APIs and configurations.
|
||||||
|
* This method should implement any setup logic specific to the device.
|
||||||
|
*
|
||||||
|
* @param config ESP LCD panel configuration.
|
||||||
|
* @param io ESP LCD panel IO handle.
|
||||||
|
* @param [out] panel ESP LCD panel handle output pointer location.
|
||||||
|
*/
|
||||||
virtual void init_panel(esp_lcd_panel_dev_config_t &config,
|
virtual void init_panel(esp_lcd_panel_dev_config_t &config,
|
||||||
esp_lcd_panel_io_handle_t io,
|
esp_lcd_panel_io_handle_t io,
|
||||||
esp_lcd_panel_handle_t &panel) = 0;
|
esp_lcd_panel_handle_t &panel) = 0;
|
||||||
|
@ -33,12 +33,27 @@
|
|||||||
#define LCD_CMD_BITS 8
|
#define LCD_CMD_BITS 8
|
||||||
#define LCD_PARAM_BITS 8
|
#define LCD_PARAM_BITS 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example of implementing the IPanelDevice interface for SSD1306 LCD device.
|
||||||
|
*/
|
||||||
class SSD1306 final : public IPanelDevice {
|
class SSD1306 final : public IPanelDevice {
|
||||||
public:
|
public:
|
||||||
// Constructors allow overriding ssd1306 config.
|
/**
|
||||||
|
* Construct a new SSD1306 device.
|
||||||
|
*
|
||||||
|
* @param i2c I2C master bus to manage this device.
|
||||||
|
*/
|
||||||
explicit SSD1306(I2C &i2c) :
|
explicit SSD1306(I2C &i2c) :
|
||||||
SSD1306(i2c, {.height = SCREEN_HEIGHT}) { }
|
SSD1306(i2c, {.height = SCREEN_HEIGHT}) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new SSD1306 device given a specific SSD1306 configuration.
|
||||||
|
*
|
||||||
|
* @param i2c I2C master bus to manage this device.
|
||||||
|
* @param config SSD1306 vendor configuration.
|
||||||
|
* @param width Width of the device screen in pixels.
|
||||||
|
* @param height Height of the device screen in pixels.
|
||||||
|
*/
|
||||||
SSD1306(I2C &i2c,
|
SSD1306(I2C &i2c,
|
||||||
esp_lcd_panel_ssd1306_config_t config,
|
esp_lcd_panel_ssd1306_config_t config,
|
||||||
int width = SCREEN_WIDTH,
|
int width = SCREEN_WIDTH,
|
||||||
@ -60,18 +75,27 @@ public:
|
|||||||
|
|
||||||
~SSD1306() final = default;
|
~SSD1306() final = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the SSD1306 vendor configuration to IPanelDevice consumers.
|
||||||
|
*
|
||||||
|
* @return Address of the SSD1306 vendor configuration structure.
|
||||||
|
*/
|
||||||
void *vendor_config() override
|
void *vendor_config() override
|
||||||
{
|
{
|
||||||
return &ssd1306_config_;
|
return &ssd1306_config_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The configuration structure specific to the SSD1306.
|
/// SSD1306 configuration structure.
|
||||||
esp_lcd_panel_ssd1306_config_t ssd1306_config_;
|
esp_lcd_panel_ssd1306_config_t ssd1306_config_;
|
||||||
|
|
||||||
// For LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data.
|
/**
|
||||||
|
* Draw buffer for this panel device.
|
||||||
|
* For LV_COLOR_FORMAT_I1 we need an extra buffer to hold the converted data.
|
||||||
|
*/
|
||||||
static uint8_t oled_buffer_[LCD_H_RES * LCD_V_RES / 8];
|
static uint8_t oled_buffer_[LCD_H_RES * LCD_V_RES / 8];
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Initializes the ESP LCD panel handle for the SSD1306 device.
|
||||||
void init_panel(esp_lcd_panel_dev_config_t &config,
|
void init_panel(esp_lcd_panel_dev_config_t &config,
|
||||||
esp_lcd_panel_io_handle_t io,
|
esp_lcd_panel_io_handle_t io,
|
||||||
esp_lcd_panel_handle_t &panel) override
|
esp_lcd_panel_handle_t &panel) override
|
||||||
|
@ -17,12 +17,6 @@
|
|||||||
* The ESP timer will be deleted when this class desctructor is called.
|
* The ESP timer will be deleted when this class desctructor is called.
|
||||||
*/
|
*/
|
||||||
struct Timer {
|
struct Timer {
|
||||||
Timer(const Timer &) = delete;
|
|
||||||
|
|
||||||
Timer(Timer &) = delete;
|
|
||||||
|
|
||||||
Timer &operator=(Timer &) = delete;
|
|
||||||
|
|
||||||
explicit Timer(esp_timer_create_args_t args) :
|
explicit Timer(esp_timer_create_args_t args) :
|
||||||
args_(args), esp_timer_(nullptr)
|
args_(args), esp_timer_(nullptr)
|
||||||
{
|
{
|
||||||
@ -36,6 +30,12 @@ struct Timer {
|
|||||||
ESP_ERROR_CHECK(esp_timer_delete(esp_timer_));
|
ESP_ERROR_CHECK(esp_timer_delete(esp_timer_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer(const Timer &) = delete;
|
||||||
|
|
||||||
|
Timer(Timer &) = delete;
|
||||||
|
|
||||||
|
Timer &operator=(Timer &) = delete;
|
||||||
|
|
||||||
/// Arguments passed to ESP API during timer creation.
|
/// Arguments passed to ESP API during timer creation.
|
||||||
esp_timer_create_args_t args_;
|
esp_timer_create_args_t args_;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ shaunrd0/klips/esp/
|
|||||||
├── 04_esp-idf-arduino # CMake example instead of Arduino IDE for ESP development.
|
├── 04_esp-idf-arduino # CMake example instead of Arduino IDE for ESP development.
|
||||||
├── 05_temp-humidity-web # Temperature and humidity sensor within a web browser.
|
├── 05_temp-humidity-web # Temperature and humidity sensor within a web browser.
|
||||||
├── 06_i2c-scanner # Simple I2C device scanner.
|
├── 06_i2c-scanner # Simple I2C device scanner.
|
||||||
|
├── 07_lcd-panel-i2c # Drawing to a LCD display with LVGL over I2C.
|
||||||
├── ESP32-basic-starter-kit.pdf # PDF for tutorials in ESP32 starter kit.
|
├── ESP32-basic-starter-kit.pdf # PDF for tutorials in ESP32 starter kit.
|
||||||
├── ESP32-dev-module.png
|
├── ESP32-dev-module.png
|
||||||
└── README.md
|
└── README.md
|
||||||
|
Loading…
x
Reference in New Issue
Block a user