mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-04-26 12:17:56 +00:00
Prices and realtime HA sensors
This commit is contained in:
@@ -594,7 +594,7 @@ void loop() {
|
|||||||
}
|
}
|
||||||
if(now - lastSysupdate > 10000) {
|
if(now - lastSysupdate > 10000) {
|
||||||
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
||||||
mqttHandler->publishSystem(&hw);
|
mqttHandler->publishSystem(&hw, eapi, &ea);
|
||||||
}
|
}
|
||||||
lastSysupdate = now;
|
lastSysupdate = now;
|
||||||
}
|
}
|
||||||
@@ -1379,7 +1379,7 @@ void MQTT_connect() {
|
|||||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
|
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
|
||||||
|
|
||||||
if(mqttHandler != NULL) {
|
if(mqttHandler != NULL) {
|
||||||
mqttHandler->publishSystem(&hw);
|
mqttHandler->publishSystem(&hw, eapi, &ea);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to the chosen MQTT topic, if set in configuration
|
// Subscribe to the chosen MQTT topic, if set in configuration
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public:
|
|||||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||||
virtual bool publishPrices(EntsoeApi* eapi);
|
virtual bool publishPrices(EntsoeApi* eapi);
|
||||||
virtual bool publishSystem(HwTools*);
|
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MQTTClient* mqtt;
|
MQTTClient* mqtt;
|
||||||
|
|||||||
@@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
|
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public:
|
|||||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||||
bool publishPrices(EntsoeApi*);
|
bool publishPrices(EntsoeApi*);
|
||||||
bool publishSystem(HwTools*);
|
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DomoticzConfig config;
|
DomoticzConfig config;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "web/root/jsonsys_json.h"
|
#include "web/root/jsonsys_json.h"
|
||||||
#include "web/root/jsonprices_json.h"
|
#include "web/root/jsonprices_json.h"
|
||||||
#include "web/root/hadiscover_json.h"
|
#include "web/root/hadiscover_json.h"
|
||||||
|
#include "web/root/realtime_json.h"
|
||||||
|
|
||||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||||
if(topic.isEmpty() || !mqtt->connected())
|
if(topic.isEmpty() || !mqtt->connected())
|
||||||
@@ -32,7 +33,7 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
|||||||
snprintf_P(json, BufferSize, HA1_JSON,
|
snprintf_P(json, BufferSize, HA1_JSON,
|
||||||
data->getActiveImportPower()
|
data->getActiveImportPower()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic + "/power", json);
|
mqtt->publish(topic + "/power", json);
|
||||||
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
|
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
|
||||||
snprintf_P(json, BufferSize, HA3_JSON,
|
snprintf_P(json, BufferSize, HA3_JSON,
|
||||||
data->getListId().c_str(),
|
data->getListId().c_str(),
|
||||||
@@ -53,9 +54,31 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
|||||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic + "/power", json);
|
mqtt->publish(topic + "/power", json);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
String peaks = "";
|
||||||
|
for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) {
|
||||||
|
if(!peaks.isEmpty()) peaks += ",";
|
||||||
|
peaks += String(ea->getPeak(i));
|
||||||
|
}
|
||||||
|
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||||
|
ea->getMonthMax(),
|
||||||
|
peaks.c_str(),
|
||||||
|
ea->getCurrentThreshold(),
|
||||||
|
ea->getUseThisHour(),
|
||||||
|
ea->getCostThisHour(),
|
||||||
|
ea->getProducedThisHour(),
|
||||||
|
ea->getUseToday(),
|
||||||
|
ea->getCostToday(),
|
||||||
|
ea->getProducedToday(),
|
||||||
|
ea->getUseThisMonth(),
|
||||||
|
ea->getCostThisMonth(),
|
||||||
|
ea->getProducedThisMonth()
|
||||||
|
);
|
||||||
|
mqtt->publish(topic + "/realtime", json);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||||
@@ -187,10 +210,10 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
ts3hr,
|
ts3hr,
|
||||||
ts6hr
|
ts6hr
|
||||||
);
|
);
|
||||||
return mqtt->publish(topic + "/prices", json);
|
return mqtt->publish(topic + "/prices", json, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||||
if(topic.isEmpty() || !mqtt->connected()){
|
if(topic.isEmpty() || !mqtt->connected()){
|
||||||
sequence = 0;
|
sequence = 0;
|
||||||
return false;
|
return false;
|
||||||
@@ -218,26 +241,42 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
|||||||
String haUrl = "http://" + haUID + ".local/";
|
String haUrl = "http://" + haUID + ".local/";
|
||||||
// Could this be necessary? haUID.replace("-", "_");
|
// Could this be necessary? haUID.replace("-", "_");
|
||||||
|
|
||||||
for(int i=0;i<sensors;i++){
|
uint8_t peaks = 0;
|
||||||
|
for(int i=0;i<HA_SENSOR_COUNT;i++) {
|
||||||
|
HomeAssistantSensor sensor = HA_SENSORS[i];
|
||||||
|
String uid = String(sensor.path);
|
||||||
|
uid.replace(".", "");
|
||||||
|
uid.replace("[", "");
|
||||||
|
uid.replace("]", "");
|
||||||
|
String uom = String(sensor.uom);
|
||||||
|
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||||
|
if(eapi == NULL) continue;
|
||||||
|
uom = String(eapi->getCurrency());
|
||||||
|
}
|
||||||
|
if(strncmp(sensor.path, "peaks[", 6) == 0) {
|
||||||
|
if(peaks >= ea->getConfig()->hours) continue;
|
||||||
|
peaks++;
|
||||||
|
}
|
||||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||||
FPSTR(HA_NAMES[i]),
|
FPSTR(sensor.name),
|
||||||
topic.c_str(), FPSTR(HA_TOPICS[i]),
|
topic.c_str(), FPSTR(sensor.topic),
|
||||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
haUID.c_str(), uid.c_str(),
|
||||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
haUID.c_str(), uid.c_str(),
|
||||||
FPSTR(HA_UOM[i]),
|
uom.c_str(),
|
||||||
FPSTR(HA_PARAMS[i]),
|
FPSTR(sensor.path),
|
||||||
FPSTR(HA_DEVCL[i]),
|
FPSTR(sensor.devcl),
|
||||||
haUID.c_str(),
|
haUID.c_str(),
|
||||||
haName.c_str(),
|
haName.c_str(),
|
||||||
haModel.c_str(),
|
haModel.c_str(),
|
||||||
VERSION,
|
VERSION,
|
||||||
haManuf.c_str(),
|
haManuf.c_str(),
|
||||||
haUrl.c_str(),
|
haUrl.c_str(),
|
||||||
strlen_P(HA_STACL[i]) > 0 ? ", \"stat_cla\" :" : "",
|
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||||
strlen_P(HA_STACL[i]) > 0 ? (char *) FPSTR(HA_STACL[i]) : ""
|
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||||
);
|
);
|
||||||
mqtt->publish(haTopic + haUID + "_" + FPSTR(HA_PARAMS[i]) + "/config", json, true, 0);
|
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
autodiscoverInit = true;
|
autodiscoverInit = true;
|
||||||
}
|
}
|
||||||
if(listType>0) sequence++;
|
if(listType>0) sequence++;
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ public:
|
|||||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||||
bool publishPrices(EntsoeApi*);
|
bool publishPrices(EntsoeApi*);
|
||||||
bool publishSystem(HwTools*);
|
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const uint8_t sensors = 17;
|
|
||||||
|
|
||||||
String haTopic = "homeassistant/sensor/";
|
String haTopic = "homeassistant/sensor/";
|
||||||
|
|
||||||
String haName = "AMS reader";
|
String haName = "AMS reader";
|
||||||
|
|||||||
@@ -3,12 +3,69 @@
|
|||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
|
|
||||||
const char* HA_TOPICS[17] PROGMEM = {"/state", "/state", "/state", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/energy", "/energy", "/energy", "/energy"};
|
struct HomeAssistantSensor {
|
||||||
const char* HA_NAMES[17] PROGMEM = {"Status", "Supply volt", "Temperature", "Active import", "Reactive import", "Active export", "Reactive export", "L1 current", "L2 current", "L3 current",
|
char* name;
|
||||||
"L1 voltage", "L2 voltage", "L3 voltage", "Accumulated active import", "Accumulated active export", "Accumulated reactive import", "Accumulated reactive export"};
|
char* topic;
|
||||||
const char* HA_PARAMS[17] PROGMEM = {"rssi", "vcc", "temp", "P", "Q", "PO", "QO", "I1", "I2", "I3", "U1", "U2", "U3", "tPI", "tPO", "tQI", "tQO"};
|
char* path;
|
||||||
const char* HA_UOM[17] PROGMEM = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A", "A", "V", "V", "V", "kWh", "kWh", "kWh", "kWh"};
|
char* uom;
|
||||||
const char* HA_DEVCL[17] PROGMEM = {"signal_strength", "voltage", "temperature", "power", "power", "power", "power", "current", "current", "current", "voltage", "voltage", "voltage", "energy", "energy", "energy", "energy"};
|
char* devcl;
|
||||||
const char* HA_STACL[17] PROGMEM = {"", "", "", "\"measurement\"", "\"measurement\"", "\"measurement\"", "\"measurement\"", "", "", "", "", "", "", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\""};
|
char* stacl;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const uint8_t HA_SENSOR_COUNT PROGMEM = 50;
|
||||||
|
HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||||
|
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||||
|
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""},
|
||||||
|
{"Temperature", "/state", "temp", "C", "temperature", "\"measurement\""},
|
||||||
|
{"Active import", "/power", "P", "W", "power", "\"measurement\""},
|
||||||
|
{"Reactive import", "/power", "Q", "VAr", "reactive_power", "\"measurement\""},
|
||||||
|
{"Active export", "/power", "PO", "W", "power", "\"measurement\""},
|
||||||
|
{"Reactive export", "/power", "QO", "VAr", "reactive_power", "\"measurement\""},
|
||||||
|
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
|
||||||
|
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
|
||||||
|
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
|
||||||
|
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
|
||||||
|
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
|
||||||
|
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""},
|
||||||
|
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Accumulated reactive import","/energy", "tQI", "kVArh","energy", "\"total_increasing\""},
|
||||||
|
{"Accumulated reactive export","/energy", "tQO", "kVArh","energy", "\"total_increasing\""},
|
||||||
|
{"Price current hour", "/prices", "prices.0", "", "monetary", ""},
|
||||||
|
{"Price next hour", "/prices", "prices.1", "", "monetary", ""},
|
||||||
|
{"Price in two hour", "/prices", "prices.2", "", "monetary", ""},
|
||||||
|
{"Price in three hour", "/prices", "prices.3", "", "monetary", ""},
|
||||||
|
{"Price in four hour", "/prices", "prices.4", "", "monetary", ""},
|
||||||
|
{"Price in five hour", "/prices", "prices.5", "", "monetary", ""},
|
||||||
|
{"Price in six hour", "/prices", "prices.6", "", "monetary", ""},
|
||||||
|
{"Price in seven hour", "/prices", "prices.7", "", "monetary", ""},
|
||||||
|
{"Price in eight hour", "/prices", "prices.8", "", "monetary", ""},
|
||||||
|
{"Price in nine hour", "/prices", "prices.9", "", "monetary", ""},
|
||||||
|
{"Price in ten hour", "/prices", "prices.10", "", "monetary", ""},
|
||||||
|
{"Price in eleven hour", "/prices", "prices.11", "", "monetary", ""},
|
||||||
|
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
|
||||||
|
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
|
||||||
|
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
|
||||||
|
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
|
||||||
|
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""},
|
||||||
|
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""},
|
||||||
|
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""},
|
||||||
|
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""},
|
||||||
|
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
|
||||||
|
{"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""},
|
||||||
|
{"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""},
|
||||||
|
{"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""},
|
||||||
|
{"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""},
|
||||||
|
{"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
return mqtt->publish(topic, json);
|
return mqtt->publish(topic, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JsonMqttHandler::publishSystem(HwTools* hw) {
|
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||||
if(init || topic.isEmpty() || !mqtt->connected())
|
if(init || topic.isEmpty() || !mqtt->connected())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public:
|
|||||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||||
bool publishPrices(EntsoeApi*);
|
bool publishPrices(EntsoeApi*);
|
||||||
bool publishSystem(HwTools*);
|
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String clientId;
|
String clientId;
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RawMqttHandler::publishSystem(HwTools* hw) {
|
bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||||
if(topic.isEmpty() || !mqtt->connected())
|
if(topic.isEmpty() || !mqtt->connected())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public:
|
|||||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||||
bool publishPrices(EntsoeApi*);
|
bool publishPrices(EntsoeApi*);
|
||||||
bool publishSystem(HwTools*);
|
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String topic;
|
String topic;
|
||||||
|
|||||||
20
web/realtime.json
Normal file
20
web/realtime.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"max" : %.1f,
|
||||||
|
"peaks" : [ %s ],
|
||||||
|
"threshold" : %d,
|
||||||
|
"hour" : {
|
||||||
|
"use" : %.2f,
|
||||||
|
"cost" : %.2f,
|
||||||
|
"produced" : %.2f
|
||||||
|
},
|
||||||
|
"day" : {
|
||||||
|
"use" : %.2f,
|
||||||
|
"cost" : %.2f,
|
||||||
|
"produced" : %.2f
|
||||||
|
},
|
||||||
|
"month" : {
|
||||||
|
"use" : %.2f,
|
||||||
|
"cost" : %.2f,
|
||||||
|
"produced" : %.2f
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user