mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-27 14:42:18 +03:00
Merge branch 'ver4dev' of https://github.com/biveraxe/IoTManager into ver4dev
This commit is contained in:
429
src/modules/exec/MySensors/MySensorsGate.cpp
Normal file
429
src/modules/exec/MySensors/MySensorsGate.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
#include "Arduino.h"
|
||||
#include "MySensorsGate.h"
|
||||
|
||||
#ifdef MYSENSORS
|
||||
// callback библиотеки mysensors
|
||||
void receive(const MyMessage& message) {
|
||||
String inMsg = String(message.getSender()) + "," + // node-id
|
||||
String(message.getSensor()) + "," + // child-sensor-id
|
||||
String(message.getType()) + "," + // type of var
|
||||
String(message.getCommand()) + "," + // command
|
||||
parseToString(message) + ";"; // value
|
||||
|
||||
// Serial.println("=>" + inMsg);
|
||||
|
||||
mysensorBuf += inMsg;
|
||||
}
|
||||
|
||||
String parseToString(const MyMessage& message) {
|
||||
String value = "error";
|
||||
switch (message.getPayloadType()) {
|
||||
case 0: // Payload type is string
|
||||
value = message.getString();
|
||||
return value;
|
||||
case 1: // Payload type is byte
|
||||
value = String(message.getByte());
|
||||
return value;
|
||||
case 2: // Payload type is INT16
|
||||
value = String(message.getInt());
|
||||
return value;
|
||||
case 3: // Payload type is UINT16
|
||||
value = String(message.getUInt());
|
||||
return value;
|
||||
case 4: // Payload type is INT32
|
||||
value = String(message.getInt());
|
||||
return value;
|
||||
case 5: // Payload type is UINT32
|
||||
value = String(message.getUInt());
|
||||
return value;
|
||||
case 6: // Payload type is binary
|
||||
value = String(message.getBool());
|
||||
return value;
|
||||
case 7: // Payload type is float32
|
||||
value = String(message.getFloat());
|
||||
return value;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
class MySensorsGate : public IoTItem {
|
||||
private:
|
||||
public:
|
||||
MySensorsGate(String parameters) : IoTItem(parameters) {
|
||||
SerialPrint("i", "MySensors", "Gate initialized");
|
||||
}
|
||||
|
||||
void doByInterval() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
loopMySensorsExecute();
|
||||
}
|
||||
|
||||
~MySensorsGate(){};
|
||||
|
||||
void loopMySensorsExecute() {
|
||||
if (mysensorBuf.length()) {
|
||||
String tmp = selectToMarker(mysensorBuf, ";");
|
||||
|
||||
String nodeId = selectFromMarkerToMarker(tmp, ",", 0); // node-id
|
||||
String childSensorId = selectFromMarkerToMarker(tmp, ",", 1); // child-sensor-id
|
||||
String type = selectFromMarkerToMarker(tmp, ",", 2); // type of var
|
||||
String command = selectFromMarkerToMarker(tmp, ",", 3); // command
|
||||
String value = selectFromMarkerToMarker(tmp, ",", 4); // value
|
||||
|
||||
static bool presentBeenStarted = false;
|
||||
|
||||
String ID = "n" + nodeId + "s" + childSensorId;
|
||||
static String infoJson = "{}";
|
||||
|
||||
if (childSensorId == "255") {
|
||||
if (command == "3") { // это особое внутреннее сообщение
|
||||
if (type == "11") { // название ноды
|
||||
SerialPrint("i", "MySensors", "===================== " + value + " =====================");
|
||||
}
|
||||
if (type == "12") { // версия ноды
|
||||
SerialPrint("i", "MySensors", "Node version: " + value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (command == "0") { // это презентация
|
||||
presentBeenStarted = true;
|
||||
int num;
|
||||
String widget;
|
||||
String descr;
|
||||
sensorType(type.toInt(), num, widget, descr);
|
||||
|
||||
descr.replace("#", " ");
|
||||
SerialPrint("i", "MySensors", "Presentation: " + ID + ": " + descr);
|
||||
}
|
||||
if (command == "1") { // это данные
|
||||
if (value != "") {
|
||||
if (presentBeenStarted) {
|
||||
presentBeenStarted = false;
|
||||
SerialPrint("i", "MySensors", "===================== " + nodeId + " =====================");
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (std::list<IoTItem*>::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) {
|
||||
if ((*it)->getID() == ID) {
|
||||
found = true;
|
||||
(*it)->setValue(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
SerialPrint("i", "MySensors", "node: " + nodeId + ", sensor: " + childSensorId + ", command: " + command + ", type: " + type + ", val: " + value + ", found: " + String(found));
|
||||
}
|
||||
}
|
||||
if (command == "2") { // это запрос значения переменной
|
||||
SerialPrint("i", "MySensors", "Request a variable value");
|
||||
}
|
||||
}
|
||||
|
||||
mysensorBuf = deleteBeforeDelimiter(mysensorBuf, ";");
|
||||
}
|
||||
}
|
||||
|
||||
void sensorType(int index, int& num, String& widget, String& descr) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
descr = F("Door#and#window#sensors");
|
||||
widget = F("alarm");
|
||||
num = 1;
|
||||
break;
|
||||
case 1:
|
||||
descr = F("Motion#sensors");
|
||||
widget = F("alarm");
|
||||
num = 1;
|
||||
break;
|
||||
case 2:
|
||||
descr = F("Smoke#sensor");
|
||||
widget = F("fillgauge");
|
||||
num = 1;
|
||||
break;
|
||||
case 3:
|
||||
descr = F("Binary#device#(on/off)");
|
||||
widget = F("toggleBtn");
|
||||
num = 2;
|
||||
break;
|
||||
case 4:
|
||||
descr = F("Dimmable#device");
|
||||
// to do
|
||||
// widget = F("range");
|
||||
// num = 2;
|
||||
break;
|
||||
case 5:
|
||||
descr = F("Window#covers#or#shades");
|
||||
// to do
|
||||
// widget = F("range");
|
||||
// num = 2;
|
||||
break;
|
||||
case 6:
|
||||
descr = F("Temperature#sensor");
|
||||
widget = F("anydataTemp");
|
||||
num = 1;
|
||||
break;
|
||||
case 7:
|
||||
descr = F("Humidity#sensor");
|
||||
widget = F("anydataHum");
|
||||
num = 1;
|
||||
break;
|
||||
case 8:
|
||||
descr = F("Pressure#sensor");
|
||||
widget = F("anydataPress");
|
||||
num = 1;
|
||||
break;
|
||||
case 9:
|
||||
descr = F("Wind#sensor");
|
||||
widget = F("anydataTime");
|
||||
num = 1;
|
||||
break;
|
||||
case 10:
|
||||
descr = F("Rain#sensor");
|
||||
widget = F("anydataTime");
|
||||
num = 1;
|
||||
break;
|
||||
case 11:
|
||||
descr = F("UV#sensor");
|
||||
widget = F("anydataTime");
|
||||
num = 1;
|
||||
break;
|
||||
case 12:
|
||||
descr = F("Weight#sensor");
|
||||
widget = F("anydataTime");
|
||||
num = 1;
|
||||
break;
|
||||
case 13:
|
||||
descr = F("Power#measuring#device");
|
||||
widget = F("anydataWtt");
|
||||
num = 1;
|
||||
break;
|
||||
case 14:
|
||||
descr = F("Heater#device");
|
||||
widget = F("anydataTemp");
|
||||
num = 1;
|
||||
break;
|
||||
case 15:
|
||||
descr = F("Distance#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 16:
|
||||
descr = F("Light#sensor");
|
||||
widget = F("anydataTime");
|
||||
num = 1;
|
||||
break;
|
||||
case 17:
|
||||
descr = F("Arduino#node#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 18:
|
||||
descr = F("Arduino#repeating#node#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 19:
|
||||
descr = F("Lock#device");
|
||||
widget = F("toggleBtn");
|
||||
num = 2;
|
||||
break;
|
||||
case 20:
|
||||
descr = F("Ir#sender/receiver#device");
|
||||
widget = F("toggleBtn");
|
||||
num = 2;
|
||||
break;
|
||||
case 21:
|
||||
descr = F("Water#meter");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 22:
|
||||
descr = F("Air#quality#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 23:
|
||||
descr = F("Custom#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 24:
|
||||
descr = F("Dust#level#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 25:
|
||||
descr = F("Scene#controller#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 26:
|
||||
descr = F("RGB#light");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 27:
|
||||
descr = F("RGBW#light#(with#separate#white#component)");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 28:
|
||||
descr = F("Color#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 29:
|
||||
descr = F("Thermostat/HVAC#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 30:
|
||||
descr = F("Multimeter#device");
|
||||
widget = F("anydataVlt");
|
||||
num = 1;
|
||||
break;
|
||||
case 31:
|
||||
descr = F("Sprinkler#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 32:
|
||||
descr = F("Water#leak#sensor");
|
||||
widget = F("alarm");
|
||||
num = 1;
|
||||
break;
|
||||
case 33:
|
||||
descr = F("Sound#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 34:
|
||||
descr = F("Vibration#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 35:
|
||||
descr = F("Moisture#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 36:
|
||||
descr = F("LCD#text#device");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 37:
|
||||
descr = F("Gas#meter");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 38:
|
||||
descr = F("GPS#Sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
case 39:
|
||||
descr = F("Water#quality#sensor");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
default:
|
||||
descr = F("Unknown");
|
||||
widget = F("anydata");
|
||||
num = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MySensorsNode : public IoTItem {
|
||||
private:
|
||||
String id = "";
|
||||
int orange = 0;
|
||||
int red = 0;
|
||||
int offline = 0;
|
||||
int _minutesPassed = 0;
|
||||
String json = "{}";
|
||||
bool dataFromNode = false;
|
||||
|
||||
public:
|
||||
MySensorsNode(String parameters) : IoTItem(parameters) {
|
||||
jsonRead(parameters, F("id"), id);
|
||||
|
||||
jsonRead(parameters, F("orange"), orange);
|
||||
jsonRead(parameters, F("red"), red);
|
||||
jsonRead(parameters, F("offline"), offline);
|
||||
|
||||
dataFromNode = false;
|
||||
SerialPrint("i", "MySensors", "Node initialized");
|
||||
}
|
||||
|
||||
void setValue(const IoTValue& Value, bool genEvent = true) {
|
||||
value = Value;
|
||||
regEvent(value.valD, "MySensorsNode", false, genEvent);
|
||||
_minutesPassed = 0;
|
||||
prevMillis = millis();
|
||||
dataFromNode = true;
|
||||
setNewWidgetAttributes();
|
||||
}
|
||||
|
||||
void doByInterval() {
|
||||
_minutesPassed++;
|
||||
setNewWidgetAttributes();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
currentMillis = millis();
|
||||
difference = currentMillis - prevMillis;
|
||||
if (difference > 60000) {
|
||||
prevMillis = millis();
|
||||
this->doByInterval();
|
||||
}
|
||||
}
|
||||
|
||||
// событие когда пользователь подключается приложением или веб интерфейсом к усройству
|
||||
void onMqttWsAppConnectEvent() {
|
||||
setNewWidgetAttributes();
|
||||
}
|
||||
|
||||
void setNewWidgetAttributes() {
|
||||
if (dataFromNode) {
|
||||
jsonWriteStr(json, F("info"), prettyMinutsTimeout(_minutesPassed));
|
||||
if (orange != 0 && red != 0 && offline != 0) {
|
||||
if (_minutesPassed < orange) {
|
||||
jsonWriteStr(json, F("color"), "");
|
||||
}
|
||||
if (_minutesPassed >= orange && _minutesPassed < red) {
|
||||
jsonWriteStr(json, F("color"), F("orange")); // сделаем виджет оранжевым
|
||||
}
|
||||
if (_minutesPassed >= red && _minutesPassed < offline) {
|
||||
jsonWriteStr(json, F("color"), F("red")); // сделаем виджет красным
|
||||
}
|
||||
if (_minutesPassed >= offline) {
|
||||
jsonWriteStr(json, F("info"), F("offline"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jsonWriteStr(json, F("info"), F("awaiting"));
|
||||
}
|
||||
sendSubWidgetsValues(id, json);
|
||||
}
|
||||
|
||||
~MySensorsNode(){};
|
||||
};
|
||||
|
||||
void* getAPI_MySensorsGate(String subtype, String param) {
|
||||
if (subtype == F("MySensorsGate")) {
|
||||
return new MySensorsGate(param);
|
||||
} else if (subtype == F("MySensorsNode")) {
|
||||
return new MySensorsNode(param);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
54
src/modules/exec/MySensors/MySensorsGate.h
Normal file
54
src/modules/exec/MySensors/MySensorsGate.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "Const.h"
|
||||
#ifdef MYSENSORS
|
||||
|
||||
/*
|
||||
* DESCRIPTION
|
||||
* The ESP32 gateway sends data received from sensors to the WiFi link.
|
||||
* The gateway also accepts input on ethernet interface, which is then sent out to the radio network.
|
||||
* ----------- PINOUT --------------
|
||||
* | IO | RF24 | RFM69 | RFM95 |
|
||||
|------|------|-------|-------|
|
||||
| MOSI | 23 | 23 | 23 |
|
||||
| MISO | 19 | 19 | 19 |
|
||||
| SCK | 18 | 18 | 18 |
|
||||
| CSN | 5 | 5 | 5 |
|
||||
| CE | 17 | - | - |
|
||||
| RST | - | 17 | 17 |
|
||||
| IRQ | 16* | 16 | 16 |
|
||||
*/
|
||||
|
||||
// Enable debug prints to serial monitor
|
||||
//#define MY_DEBUG
|
||||
|
||||
//#define MY_RF24_CE_PIN 26 // Двигать пин CE на D4 чтобы освободить I2C
|
||||
//#define MY_RF24_CS_PIN 9
|
||||
|
||||
// Use a bit lower baudrate for serial prints on ESP8266 than default in MyConfig.h
|
||||
#define MY_BAUD_RATE 115200
|
||||
|
||||
// Enables and select radio type (if attached)
|
||||
#define MY_RADIO_RF24
|
||||
//#define MY_RADIO_RFM69
|
||||
//#define MY_RADIO_RFM95
|
||||
|
||||
//#define MY_RF24_DATARATE RF24_1MBPS // Для платы KeyWish скорость 1 мегабит
|
||||
|
||||
// How many clients should be able to connect to this gateway (default 1)
|
||||
#define MY_GATEWAY_MAX_CLIENTS 10
|
||||
|
||||
// Set LOW transmit power level as default, if you have an amplified NRF-module and
|
||||
// power your radio separately with a good regulator you can turn up PA level.
|
||||
#define MY_RF24_PA_LEVEL RF24_PA_MAX
|
||||
|
||||
// используем гейт в режиме serial хотя нам этот режим не нужен, поэтому в библиотеки отключаем MY_SERIALDEVICE.print
|
||||
// в файле MyGatewayTransportSerial.cpp в строчке 35
|
||||
#define MY_GATEWAY_SERIAL
|
||||
|
||||
//#define CHILD_ID 1
|
||||
|
||||
#include <MySensors.h>
|
||||
|
||||
extern String parseToString(const MyMessage& message);
|
||||
|
||||
#endif
|
||||
54
src/modules/exec/MySensors/modinfo.json
Normal file
54
src/modules/exec/MySensors/modinfo.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"menuSection": "Исполнительные устройства",
|
||||
"configItem": [
|
||||
{
|
||||
"global": 0,
|
||||
"name": "MySensorsGate",
|
||||
"type": "Reading",
|
||||
"subtype": "MySensorsGate",
|
||||
"id": "gt",
|
||||
"widget": "nil",
|
||||
"page": "",
|
||||
"descr": ""
|
||||
},
|
||||
{
|
||||
"global": 0,
|
||||
"name": "MySensorsNode",
|
||||
"type": "Reading",
|
||||
"subtype": "MySensorsNode",
|
||||
"id": "n",
|
||||
"widget": "anydataTmp",
|
||||
"page": "MySensors",
|
||||
"descr": "Температура",
|
||||
"orange": 60,
|
||||
"red": 120,
|
||||
"offline": 180,
|
||||
"round": 1
|
||||
}
|
||||
],
|
||||
"about": {
|
||||
"authorName": "Dmitry Borisenko",
|
||||
"authorContact": "https://t.me/Dmitry_Borisenko",
|
||||
"authorGit": "https://github.com/DmitryBorisenko33",
|
||||
"specialThanks": "",
|
||||
"moduleName": "MySensorsGate",
|
||||
"moduleVersion": "1.0",
|
||||
"usedRam": {
|
||||
"esp32_4mb": 15,
|
||||
"esp8266_4mb": 0
|
||||
},
|
||||
"title": "Гейт MySensors",
|
||||
"moduleDesc": "Устройство состоит из esp32 и подключенному к нему радиомодулю NRF24L01. Вместе в связке они образуют гейт, способный принимать данные датчиков. Датчики способны работать до нескольких лет на батарейках. Датчики делаются на базе nrf52832 от holyiot. Батарейки подключаются напрямик к nrf52832",
|
||||
"retInfo": "",
|
||||
"propInfo": {
|
||||
"id": "Для настройки следует выбрать один раз MySensorsGate и выбрать сколько необходимо раз MySensorsNode. Вместо ID нужно указать например - n100s1. Это значит что мы будем получать данные с ноды 100 и с сенсора этой ноды под номером 1",
|
||||
"orange": "количество минут после которого окрасить виджет в оранжевый цвет",
|
||||
"red": "количество минут после которого окрасить виджет в красный цвет",
|
||||
"offline": "количество минут после которого отобразить что устройство offline, если все три orange red и offline поставить в ноль - то функция окраски выключится"
|
||||
}
|
||||
},
|
||||
"defActive": false,
|
||||
"usedLibs": {
|
||||
"esp32_4mb": []
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,19 @@
|
||||
#include "Global.h"
|
||||
#include "classes/IoTItem.h"
|
||||
|
||||
class TelegramLT : public IoTItem
|
||||
{
|
||||
|
||||
public:
|
||||
class TelegramLT : public IoTItem {
|
||||
public:
|
||||
String _prevMsg = "";
|
||||
String _token;
|
||||
String _chatID;
|
||||
|
||||
TelegramLT(String parameters) : IoTItem(parameters)
|
||||
{
|
||||
TelegramLT(String parameters) : IoTItem(parameters) {
|
||||
jsonRead(parameters, "token", _token);
|
||||
jsonRead(parameters, "chatID", _chatID);
|
||||
}
|
||||
|
||||
void sendTelegramMsg(bool often, String msg)
|
||||
{
|
||||
if (WiFi.status() == WL_CONNECTED && (often || !often && _prevMsg != msg))
|
||||
{
|
||||
void sendTelegramMsg(bool often, String msg) {
|
||||
if (WiFi.status() == WL_CONNECTED && (often || !often && _prevMsg != msg)) {
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, "http://live-control.com/iotm/telegram.php");
|
||||
@@ -29,14 +24,11 @@ public:
|
||||
SerialPrint("<-", F("Telegram"), "chat ID: " + _chatID + ", msg: " + msg);
|
||||
SerialPrint("->", F("Telegram"), "chat ID: " + _chatID + ", server: " + httpResponseCode);
|
||||
|
||||
if (!strstr(payload.c_str(), "{\"ok\":true"))
|
||||
{
|
||||
if (!strstr(payload.c_str(), "{\"ok\":true")) {
|
||||
value.valD = 0;
|
||||
Serial.printf("Telegram error, msg from server: %s\n", payload.c_str());
|
||||
regEvent(value.valD, payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
value.valD = 1;
|
||||
regEvent(value.valD, payload);
|
||||
}
|
||||
@@ -45,27 +37,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m)
|
||||
{
|
||||
if (param.size() == 1)
|
||||
{
|
||||
IoTValue execute(String command, std::vector<IoTValue> ¶m) {
|
||||
if (param.size() == 1) {
|
||||
String strTmp;
|
||||
if (param[0].isDecimal && param[0].valS == "")
|
||||
strTmp = param[0].valD;
|
||||
else
|
||||
strTmp = param[0].valS;
|
||||
|
||||
if (command == "sendMsg")
|
||||
{
|
||||
if (param.size())
|
||||
{
|
||||
if (command == "sendMsg") {
|
||||
if (param.size()) {
|
||||
sendTelegramMsg(false, strTmp);
|
||||
}
|
||||
}
|
||||
else if (command == "sendOftenMsg")
|
||||
{
|
||||
if (param.size())
|
||||
{
|
||||
} else if (command == "sendOftenMsg") {
|
||||
if (param.size()) {
|
||||
sendTelegramMsg(true, strTmp);
|
||||
}
|
||||
}
|
||||
@@ -76,14 +61,10 @@ public:
|
||||
~TelegramLT(){};
|
||||
};
|
||||
|
||||
void *getAPI_TelegramLT(String subtype, String param)
|
||||
{
|
||||
if (subtype == F("TelegramLT"))
|
||||
{
|
||||
void *getAPI_TelegramLT(String subtype, String param) {
|
||||
if (subtype == F("TelegramLT")) {
|
||||
return new TelegramLT(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user