Added MQTT
Updated readme for 0.8.0 Fixed custom theme bug Bumped version codes to 0.8.0
This commit is contained in:
parent
473991638c
commit
eeb17b417c
26
readme.md
26
readme.md
@ -1,17 +1,20 @@
|
||||
![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png)
|
||||
|
||||
## Welcome to my project WLED!
|
||||
|
||||
WLED is a fast and (relatively) secure implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B) LEDs!
|
||||
WLED is a fast, advanced and (relatively) secure implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B) LEDs!
|
||||
|
||||
### Features: (V0.7.1)
|
||||
### Features: (v0.8.0)
|
||||
- RGB, HSB, and brightness sliders
|
||||
- All new, mobile-friendly web UI!
|
||||
- Settings page - configuration over network
|
||||
- Access Point and station mode - automatic failsafe AP
|
||||
- Support of Blynk IoT cloud
|
||||
- WS2812FX library integrated for over 50 special effects (+Custom Theater Chase)!
|
||||
- Support of Blynk IoT cloud and MQTT
|
||||
- WS2812FX library integrated for over 70 special effects (with FastLED palettes)!
|
||||
- Secondary color support lets you use even more effect combinations
|
||||
- Alexa smart home device server (including dimming)
|
||||
- Beta syncronization to Philips hue lights
|
||||
- Realtime UDP Packet Control (E1.31, Hyperion, WARLS, DRGB, DRGBW)
|
||||
- Support for RGBW strips
|
||||
- 25 user presets! Save colors and effects and apply them easily! Supports cycling through them.
|
||||
- HTTP request API for simple integration
|
||||
@ -24,7 +27,6 @@ WLED is a fast and (relatively) secure implementation of an ESP8266/ESP32 webser
|
||||
- Password protected OTA page for added security (OTA lock)
|
||||
- NTP and configurable analog clock function
|
||||
- Support for the Cronixie Clock kit by Diamex
|
||||
- Realtime UDP Packet Control (E1.31, Hyperion, WARLS, DRGB, DRGBW)
|
||||
|
||||
### Quick start guide and documentation:
|
||||
|
||||
@ -33,15 +35,13 @@ See the [wiki](https://github.com/Aircoookie/WLED/wiki)!
|
||||
### Other
|
||||
|
||||
Licensed under the MIT license
|
||||
Uses libraries:
|
||||
ESP8266/ESP32 Arduino Core
|
||||
NeoPixelBus by Makuna
|
||||
[WS2812FX](https://github.com/kitesurfer1404/WS2812FX) by kitesurfer1404 (Aircoookie fork)
|
||||
Time library
|
||||
Timezone library by JChristensen
|
||||
Alexa code based on arduino-esp8266-alexa-multiple-wemo-switch by kakopappa
|
||||
Credits in About page!
|
||||
|
||||
Uses Linearicons by Perxis! (link in settings page)
|
||||
Uses Linearicons by Perxis!
|
||||
|
||||
Join the Discord [server](https://discord.gg/KuqP7NE) to discuss everything about WLED!
|
||||
You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com).
|
||||
If you insist that you just love WLED too much, you can [send me a gift](https://paypal.me/aircoookie)
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head><meta charset="utf-8"><meta name="theme-color" content="#fff">
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico'/>
|
||||
<title>WLED 0.8.0-a</title>
|
||||
<title>WLED 0.8.0</title>
|
||||
<script>
|
||||
var d=document;
|
||||
var w=window.getComputedStyle(d.querySelector("html"));
|
||||
|
@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#333333">
|
||||
<meta content="yes" name="apple-mobile-web-app-capable">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAGACGAAAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAE1JREFUOI1j/P//PwOxgNGeAUMxE9G6cQCKDWAhpADZ2f8PMjBS3QW08QK20KaZC2gfC9hCnqouoNgARgY7zMxAyNlUdQHlXiAlO2MDAD63EVqNHAe0AAAAAElFTkSuQmCC"/>
|
||||
<title>WLED 0.8.0-a</title>
|
||||
<title>WLED 0.8.0</title>
|
||||
<style>
|
||||
*{transition-duration: 0.5s;}
|
||||
body {
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -250,6 +250,11 @@ Alexa invocation name: <input name="AI" maxlength="32">
|
||||
<h3>Blynk</h3>
|
||||
Device Auth token: <input name="BK" maxlength="33"><br>
|
||||
<i>Clear the token field to disable. </i><a href="https://github.com/Aircoookie/WLED/wiki/Blynk" target="_blank">Setup info</a>
|
||||
<h3>MQTT</h3>
|
||||
Broker: <input name="MS" maxlength="32"><br>
|
||||
Device Topic: <input name="MD" maxlength="32"><br>
|
||||
Group Topic: <input name="MG" maxlength="32"><br>
|
||||
<i>Reboot required to apply changes. </i><a href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info</a>
|
||||
<h3>Philips Hue</h3>
|
||||
<i>You can find the bridge IP and the light number in the 'About' section of the hue app.</i><br>
|
||||
Poll Hue light <input name="HL" type="number" min="1" max="99" required> every <input name="HI" type="number" min="100" max="65000" required> ms: <input type="checkbox" name="HP"><br>
|
||||
@ -406,7 +411,7 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form
|
||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
|
||||
<h3>About</h3>
|
||||
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.8.0-a<br><br>
|
||||
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.8.0<br><br>
|
||||
<b>Contributors:</b><br>
|
||||
StormPie <i>(Mobile HTML UI)</i><br><br>
|
||||
Thank you so much!<br><br>
|
||||
@ -421,6 +426,7 @@ Thank you so much!<br><br>
|
||||
<i><a href="https://github.com/JChristensen/Timezone" target="_blank">Timezone</a> library by JChristensen</i><br>
|
||||
<i><a href="https://github.com/blynkkk/blynk-library" target="_blank">Blynk</a> library (compacted)</i><br>
|
||||
<i><a href="https://github.com/forkineye/E131" target="_blank">E1.31</a> library by forkineye (modified)</i><br>
|
||||
<i><a href="https://github.com/knolleary/pubsubclient" target="_blank">PubSubClient</a> by knolleary (modified)</i><br>
|
||||
<i><a href="https://github.com/Aircoookie/Espalexa" target="_blank">Espalexa</a> by Aircoookie (modified)</i><br><br>
|
||||
<i>UI icons by <a href="https://linearicons.com" target="_blank">Linearicons</a> created by <a href="https://perxis.com" target="_blank">Perxis</a>! (CC-BY-SA 4.0)</i> <br><br>
|
||||
Server message: <span class="msg"> Response error! </span><hr>
|
||||
|
@ -3,7 +3,7 @@
|
||||
*/
|
||||
/*
|
||||
* @title WLED project sketch
|
||||
* @version 0.8.0-a
|
||||
* @version 0.8.0
|
||||
* @author Christian Schwinne
|
||||
*/
|
||||
|
||||
@ -45,8 +45,8 @@
|
||||
|
||||
|
||||
//version code in format yymmddb (b = daily build)
|
||||
#define VERSION 1810011
|
||||
char versionString[] = "0.8.0-a";
|
||||
#define VERSION 1810031
|
||||
char versionString[] = "0.8.0";
|
||||
|
||||
|
||||
//AP and OTA default passwords (for maximum change them!)
|
||||
@ -59,7 +59,7 @@ char otaPass[33] = "wledota";
|
||||
|
||||
|
||||
//to toggle usb serial debug (un)comment following line(s)
|
||||
#define DEBUG
|
||||
//#define DEBUG
|
||||
|
||||
|
||||
//Hardware CONFIG (only changeble HERE, not at runtime)
|
||||
@ -111,7 +111,7 @@ byte nightlightDelayMins = 60;
|
||||
bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over
|
||||
bool fadeTransition = true; //enable crossfading color transition
|
||||
bool enableSecTransition = true; //also enable transition for secondary color
|
||||
uint16_t transitionDelay = 1200; //default crossfade duration in ms
|
||||
uint16_t transitionDelay = 900; //default crossfade duration in ms
|
||||
|
||||
bool reverseMode = false; //flip entire LED strip (reverses all effect directions)
|
||||
bool initLedsLast = false; //turn on LEDs only after WiFi connected/AP open
|
||||
@ -161,9 +161,9 @@ bool e131Enabled = true; //settings for E1.31 (sACN) protoc
|
||||
uint16_t e131Universe = 1;
|
||||
bool e131Multicast = false;
|
||||
|
||||
char mqttTopic0[33] = ""; //main MQTT topic (individual per device, default is wled/mac)
|
||||
char mqttTopic1[33] = "wled/all"; //second MQTT topic (for example to group devices)
|
||||
char mqttServer[33] = "37.187.106.16"; //both domains and IPs should work (no SSL) 37.187.106.16
|
||||
char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac)
|
||||
char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices)
|
||||
char mqttServer[33] = ""; //both domains and IPs should work (no SSL)
|
||||
|
||||
bool huePollingEnabled = false; //poll hue bridge for light state
|
||||
uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response
|
||||
@ -272,7 +272,7 @@ bool onlyAP = false; //only Access Point active, no con
|
||||
bool udpConnected = false, udpRgbConnected = false;
|
||||
|
||||
//ui style
|
||||
char cssCol[9][5]={"","","","","",""};
|
||||
char cssCol[6][9]={"","","","","",""};
|
||||
String cssColorString="";
|
||||
bool showWelcomePage = false;
|
||||
|
||||
@ -332,6 +332,9 @@ unsigned long realtimeTimeout = 0;
|
||||
//mqtt
|
||||
bool mqttInit = false;
|
||||
long lastMQTTReconnectAttempt = 0;
|
||||
long lastInterfaceUpdate = 0;
|
||||
byte interfaceUpdateCallMode = 0;
|
||||
uint32_t mqttFailedConAttempts = 0;
|
||||
|
||||
//auxiliary debug pin
|
||||
byte auxTime = 0;
|
||||
|
@ -6,7 +6,7 @@
|
||||
#define EEPSIZE 3072
|
||||
|
||||
//eeprom Version code, enables default settings instead of 0 init on update
|
||||
#define EEPVER 8
|
||||
#define EEPVER 9
|
||||
//0 -> old version, default
|
||||
//1 -> 0.4p 1711272 and up
|
||||
//2 -> 0.4p 1711302 and up
|
||||
@ -15,7 +15,8 @@
|
||||
//5 -> 0.5.1 and up
|
||||
//6 -> 0.6.0 and up
|
||||
//7 -> 0.7.1 and up
|
||||
//8 -> 0.8.0 and up
|
||||
//8 -> 0.8.0-a and up
|
||||
//9 -> 0.8.0
|
||||
|
||||
/*
|
||||
* Erase all configuration data
|
||||
@ -147,7 +148,7 @@ void saveSettingsToEEPROM()
|
||||
int in = 900+k*8;
|
||||
for (int i=in; i < in+8; ++i)
|
||||
{
|
||||
EEPROM.write(i, cssCol[i-in][k]);
|
||||
EEPROM.write(i, cssCol[k][i-in]);
|
||||
}}
|
||||
|
||||
EEPROM.write(948,currentTheme);
|
||||
@ -243,6 +244,19 @@ void saveSettingsToEEPROM()
|
||||
EEPROM.write(2290 + i, timerMacro[i] );
|
||||
}
|
||||
|
||||
for (int i = 2300; i < 2333; ++i)
|
||||
{
|
||||
EEPROM.write(i, mqttServer[i-2300]);
|
||||
}
|
||||
for (int i = 2333; i < 2366; ++i)
|
||||
{
|
||||
EEPROM.write(i, mqttDeviceTopic[i-2333]);
|
||||
}
|
||||
for (int i = 2366; i < 2399; ++i)
|
||||
{
|
||||
EEPROM.write(i, mqttGroupTopic[i-2366]);
|
||||
}
|
||||
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
@ -469,6 +483,25 @@ void loadSettingsFromEEPROM(bool first)
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEEPROMversion > 8)
|
||||
{
|
||||
for (int i = 2300; i < 2333; ++i)
|
||||
{
|
||||
mqttServer[i-2300] = EEPROM.read(i);
|
||||
if (mqttServer[i-2300] == 0) break;
|
||||
}
|
||||
for (int i = 2333; i < 2366; ++i)
|
||||
{
|
||||
mqttDeviceTopic[i-2333] = EEPROM.read(i);
|
||||
if (mqttDeviceTopic[i-2333] == 0) break;
|
||||
}
|
||||
for (int i = 2366; i < 2399; ++i)
|
||||
{
|
||||
mqttGroupTopic[i-2366] = EEPROM.read(i);
|
||||
if (mqttGroupTopic[i-2366] == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
receiveDirect = !EEPROM.read(2200);
|
||||
enableRealtimeUI = EEPROM.read(2201);
|
||||
uiConfiguration = EEPROM.read(2202);
|
||||
@ -492,12 +525,6 @@ void loadSettingsFromEEPROM(bool first)
|
||||
presetApplyFx = EEPROM.read(2212);
|
||||
}
|
||||
|
||||
for (int i = 2220; i < 2255; ++i)
|
||||
{
|
||||
blynkApiKey[i-2220] = EEPROM.read(i);
|
||||
if (blynkApiKey[i-2220] == 0) break;
|
||||
}
|
||||
|
||||
bootPreset = EEPROM.read(389);
|
||||
wifiLock = EEPROM.read(393);
|
||||
utcOffsetSecs = ((EEPROM.read(394) << 0) & 0xFF) + ((EEPROM.read(395) << 8) & 0xFF00);
|
||||
@ -514,12 +541,18 @@ void loadSettingsFromEEPROM(bool first)
|
||||
for (int i=in; i < in+8; ++i)
|
||||
{
|
||||
if (EEPROM.read(i) == 0) break;
|
||||
cssCol[i-in][k] =EEPROM.read(i);
|
||||
cssCol[k][i-in] =EEPROM.read(i);
|
||||
}}
|
||||
|
||||
//custom macro memory (16 slots/ each 64byte)
|
||||
//1024-2047 reserved
|
||||
|
||||
for (int i = 2220; i < 2255; ++i)
|
||||
{
|
||||
blynkApiKey[i-2220] = EEPROM.read(i);
|
||||
if (blynkApiKey[i-2220] == 0) break;
|
||||
}
|
||||
|
||||
//user MOD memory
|
||||
//2944 - 3071 reserved
|
||||
|
||||
|
@ -241,7 +241,9 @@ void getSettingsJS(byte subPage) //get values for settings form in javascript
|
||||
sappend('c',"AL",alexaEnabled);
|
||||
sappends('s',"AI",alexaInvocationName);
|
||||
sappend('c',"SA",notifyAlexa);
|
||||
sappends('s',"BK",(char*)((blynkEnabled)?"Hidden":""));
|
||||
sappends('s',"MS",mqttServer);
|
||||
sappends('s',"MD",mqttDeviceTopic);
|
||||
sappends('s',"MG",mqttGroupTopic);
|
||||
sappend('v',"H0",hueIP[0]);
|
||||
sappend('v',"H1",hueIP[1]);
|
||||
sappend('v',"H2",hueIP[2]);
|
||||
|
@ -171,6 +171,10 @@ void handleSettingsSet(byte subPage)
|
||||
|
||||
if (server.hasArg("BK") && !server.arg("BK").equals("Hidden")) {strcpy(blynkApiKey,server.arg("BK").c_str()); initBlynk(blynkApiKey);}
|
||||
|
||||
strcpy(mqttServer, server.arg("MS").c_str());
|
||||
strcpy(mqttDeviceTopic, server.arg("MD").c_str());
|
||||
strcpy(mqttGroupTopic, server.arg("MG").c_str());
|
||||
|
||||
notifyHue = server.hasArg("SH");
|
||||
for (int i=0;i<4;i++){
|
||||
String a = "H"+String(i);
|
||||
|
@ -58,7 +58,7 @@ void wledInit()
|
||||
}
|
||||
|
||||
prepareIds(); //UUID from MAC (for Alexa and MQTT)
|
||||
if (mqttTopic0[0] == 0) strcpy(mqttTopic0, strcat("wled/", escapedMac.c_str()));
|
||||
if (mqttDeviceTopic[0] == 0) strcpy(mqttDeviceTopic, strcat("wled/", escapedMac.c_str()));
|
||||
if (!onlyAP) mqttInit = initMQTT();
|
||||
|
||||
if (!initLedsLast) strip.service();
|
||||
|
@ -92,7 +92,9 @@ void colorUpdated(int callMode)
|
||||
whiteSecIT = whiteSec;
|
||||
briIT = bri;
|
||||
if (bri > 0) briLast = bri;
|
||||
|
||||
notify(callMode);
|
||||
|
||||
if (fadeTransition)
|
||||
{
|
||||
//set correct delay if not using notification delay
|
||||
@ -120,11 +122,33 @@ void colorUpdated(int callMode)
|
||||
setLedsStandard();
|
||||
strip.trigger();
|
||||
}
|
||||
if (callMode != 9 && callMode != 5 && callMode != 8) updateBlynk();
|
||||
|
||||
if (callMode == 8) return;
|
||||
//only update Blynk and mqtt every 2 seconds to reduce lag
|
||||
if (millis() - lastInterfaceUpdate <= 2000)
|
||||
{
|
||||
interfaceUpdateCallMode = callMode;
|
||||
return;
|
||||
}
|
||||
updateInterfaces(callMode);
|
||||
}
|
||||
|
||||
void updateInterfaces(uint8_t callMode)
|
||||
{
|
||||
if (callMode != 9 && callMode != 5) updateBlynk();
|
||||
publishMQTT();
|
||||
lastInterfaceUpdate = millis();
|
||||
}
|
||||
|
||||
void handleTransitions()
|
||||
{
|
||||
//handle still pending interface update
|
||||
if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > 2000)
|
||||
{
|
||||
updateInterfaces(interfaceUpdateCallMode);
|
||||
interfaceUpdateCallMode = 0; //disable
|
||||
}
|
||||
|
||||
if (transitionActive && transitionDelayTemp > 0)
|
||||
{
|
||||
float tper = (millis() - transitionStartTime)/(float)transitionDelayTemp;
|
||||
|
@ -28,23 +28,38 @@ void callbackMQTT(char* topic, byte* payload, unsigned int length) {
|
||||
colorUpdated(1);
|
||||
} else if (strstr(topic, "/api"))
|
||||
{
|
||||
handleSet(String((char*)payload));
|
||||
String apireq = "win&";
|
||||
handleSet(apireq += (char*)payload));
|
||||
} else
|
||||
{
|
||||
parseMQTTBriPayload((char*)payload);
|
||||
}
|
||||
}
|
||||
|
||||
void publishStatus()
|
||||
void publishMQTT()
|
||||
{
|
||||
if (!mqtt.connected()) return;
|
||||
DEBUG_PRINTLN("Publish MQTT");
|
||||
|
||||
char s[4];
|
||||
char s[10];
|
||||
char subuf[38];
|
||||
|
||||
sprintf(s, "%ld", bri);
|
||||
mqtt.publish(strcat(mqttTopic0, "/g") , s);
|
||||
XML_response(false);
|
||||
mqtt.publish(strcat(mqttTopic0, "/vs"), obuf);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, "/g");
|
||||
mqtt.publish(subuf, s);
|
||||
|
||||
sprintf(s, "#%X", white*16777216 + col[0]*65536 + col[1]*256 + col[2]);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, "/c");
|
||||
mqtt.publish(subuf, s);
|
||||
|
||||
//if you want to use this, increase the MQTT buffer in PubSubClient.h to 350+
|
||||
//it will publish the API response to MQTT
|
||||
/*XML_response(false);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, "/v");
|
||||
mqtt.publish(subuf, obuf);*/
|
||||
}
|
||||
|
||||
bool reconnectMQTT()
|
||||
@ -53,26 +68,26 @@ bool reconnectMQTT()
|
||||
{
|
||||
//re-subscribe to required topics
|
||||
char subuf[38];
|
||||
strcpy(subuf, mqttTopic0);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
|
||||
if (mqttTopic0[0] != 0)
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
{
|
||||
strcpy(subuf, mqttTopic0);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
mqtt.subscribe(subuf);
|
||||
strcat(subuf, "/col");
|
||||
mqtt.subscribe(subuf);
|
||||
strcpy(subuf, mqttTopic0);
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, "/api");
|
||||
mqtt.subscribe(subuf);
|
||||
}
|
||||
|
||||
if (mqttTopic1[0] != 0)
|
||||
if (mqttGroupTopic[0] != 0)
|
||||
{
|
||||
strcpy(subuf, mqttTopic1);
|
||||
strcpy(subuf, mqttGroupTopic);
|
||||
mqtt.subscribe(subuf);
|
||||
strcat(subuf, "/col");
|
||||
mqtt.subscribe(subuf);
|
||||
strcpy(subuf, mqttTopic1);
|
||||
strcpy(subuf, mqttGroupTopic);
|
||||
strcat(subuf, "/api");
|
||||
mqtt.subscribe(subuf);
|
||||
}
|
||||
@ -100,12 +115,20 @@ bool initMQTT()
|
||||
void handleMQTT()
|
||||
{
|
||||
if (WiFi.status() != WL_CONNECTED || !mqttInit) return;
|
||||
if (!mqtt.connected() && millis() - lastMQTTReconnectAttempt > 5000)
|
||||
|
||||
//every time connection is unsuccessful, the attempt interval is increased, since attempt will block program for 7 sec each time
|
||||
if (!mqtt.connected() && millis() - lastMQTTReconnectAttempt > 5000 + (5000 * mqttFailedConAttempts * mqttFailedConAttempts))
|
||||
{
|
||||
DEBUG_PRINTLN("Attempting to connect MQTT...");
|
||||
lastMQTTReconnectAttempt = millis();
|
||||
if (!reconnectMQTT()) return;
|
||||
if (!reconnectMQTT())
|
||||
{
|
||||
//still attempt reconnect about once daily
|
||||
if (mqttFailedConAttempts < 120) mqttFailedConAttempts++;
|
||||
return;
|
||||
}
|
||||
DEBUG_PRINTLN("MQTT con!");
|
||||
mqttFailedConAttempts = 0;
|
||||
}
|
||||
mqtt.loop();
|
||||
}
|
||||
|
BIN
wled_logo.png
Normal file
BIN
wled_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Loading…
Reference in New Issue
Block a user