mirror of
https://github.com/eddyem/zeiss_utils.git
synced 2025-12-06 02:35:15 +03:00
add web-interface
This commit is contained in:
parent
cbec6c1f63
commit
d348acd7f9
@ -55,6 +55,9 @@
|
||||
// minimal & maximal focus positions (should be >min+dF0 & <max-dF0)
|
||||
#define FOCMIN 200
|
||||
#define FOCMAX 320000
|
||||
// focus raw positions @ endswitches (< or >)
|
||||
#define FOCPOS_CW_ESW 5000
|
||||
#define FOCPOS_CCW_ESW 315000
|
||||
// the same in mm
|
||||
#define FOCMIN_MM 0.1
|
||||
#define FOCMAX_MM 76.0
|
||||
|
||||
@ -35,11 +35,30 @@ static unsigned long motor_id = 0, motor_p_id = 0;//, bcast_id = 1;
|
||||
static unsigned long curposition = 0;
|
||||
// encoder's node number
|
||||
static int encnodenum = 0;
|
||||
|
||||
// system status
|
||||
static sysstatus curstatus = STAT_OK;
|
||||
static canstatus can_write_par(uint8_t subidx, uint16_t idx, uint32_t *parval);
|
||||
static canstatus can_read_par(uint8_t subidx, uint16_t idx, uint32_t *parval);
|
||||
static int move(unsigned long targposition, int16_t rawspeed);
|
||||
|
||||
// return 0 if all OK
|
||||
static int chk_eswstates(){
|
||||
uint32_t cw, ccw;
|
||||
if(CAN_NOERR != can_read_par(PAR_DI_SUBIDX, PAR_CW_IDX, &cw)) goto verybad;
|
||||
if(CAN_NOERR != can_read_par(PAR_DI_SUBIDX, PAR_CCW_IDX, &ccw)) goto verybad;
|
||||
uint32_t parval = DI_ENSTOP;
|
||||
if(cw != DI_ENSTOP || ccw != DI_ENSTOP){ // activate enable/stop
|
||||
WARNX("\n\nThe end-switches state wasn't default!");
|
||||
if(CAN_NOERR != can_write_par(PAR_DI_SUBIDX, PAR_CW_IDX, &parval)) goto verybad;
|
||||
if(CAN_NOERR != can_write_par(PAR_DI_SUBIDX, PAR_CCW_IDX, &parval)) goto verybad;
|
||||
}
|
||||
return 0;
|
||||
verybad:
|
||||
WARNX("Can't return esw to default state");
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief init_encoder - encoder's interface initialisation
|
||||
* @param encnode - encoder's node number
|
||||
@ -107,6 +126,7 @@ int init_encoder(int encnode, int reset){
|
||||
else for(i=0;i<n;i++)
|
||||
verbose("Node%d PDO%d %08lx (%ld)\n",node[i],pdo_n[i],pdo_v[i],pdo_v[n]);
|
||||
}while(0);
|
||||
curstatus = STAT_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -132,12 +152,15 @@ int go_out_from_ESW(){
|
||||
}
|
||||
if(e == ESW_BOTH_ACTIVE){ // error situation!
|
||||
WARNX("Error: both end-switches are active!");
|
||||
curstatus = STAT_BOTHESW;
|
||||
return 1;
|
||||
}
|
||||
if(e == ESW_INACTIVE){
|
||||
DBG("Esw inactive");
|
||||
curstatus = STAT_OK;
|
||||
return 0;
|
||||
}
|
||||
curstatus = STAT_GOFROMESW;
|
||||
// try to move from esw
|
||||
parval = DI_NOFUNC;
|
||||
uint16_t idx = (e == ESW_CW_ACTIVE) ? PAR_CW_IDX : PAR_CCW_IDX;
|
||||
@ -147,7 +170,6 @@ int go_out_from_ESW(){
|
||||
getPos(NULL);
|
||||
DBG("try %d, pos: %lu, E=%d", i, curposition, e);
|
||||
unsigned long targ = (e == ESW_CW_ACTIVE) ? curposition - (double)FOCSCALE_MM*0.2 : curposition + (double)FOCSCALE_MM*0.2;
|
||||
|
||||
if(move(targ, speed)) continue;
|
||||
get_endswitches(&e);
|
||||
if(e == ESW_INACTIVE) break;
|
||||
@ -158,11 +180,14 @@ int go_out_from_ESW(){
|
||||
if(CAN_NOERR != can_write_par(PAR_DI_SUBIDX, PAR_CCW_IDX, &parval)) goto bad;
|
||||
if(e != ESW_INACTIVE){
|
||||
WARNX("Can't move out of end-switch");
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
curstatus = STAT_OK;
|
||||
return 0;
|
||||
bad:
|
||||
WARNX("Can't get/set esw parameters");
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -193,6 +218,26 @@ int init_motor_ids(int addr){
|
||||
int getPos(double *pos){
|
||||
int r = !(getLong(encnodenum, DS406_POSITION_VAL, 0, &curposition));
|
||||
if(pos) *pos = FOC_RAW2MM(curposition);
|
||||
eswstate e;
|
||||
if(CAN_NOERR != get_endswitches(&e)){
|
||||
WARNX("Can't read end-switches state");
|
||||
curstatus = STAT_ERROR;
|
||||
}else switch(e){
|
||||
case ESW_BOTH_ACTIVE:
|
||||
curstatus = STAT_BOTHESW;
|
||||
break;
|
||||
case ESW_CCW_ACTIVE:
|
||||
if(curposition > FOCPOS_CCW_ESW) curstatus = STAT_BADESW;
|
||||
else curstatus = STAT_ESW;
|
||||
break;
|
||||
case ESW_CW_ACTIVE:
|
||||
if(curposition < FOCPOS_CW_ESW) curstatus = STAT_BADESW;
|
||||
else curstatus = STAT_ESW;
|
||||
break;
|
||||
case ESW_INACTIVE:
|
||||
default:
|
||||
curstatus = STAT_OK;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -433,6 +478,7 @@ int stop(){
|
||||
* @return 0 if all OK
|
||||
*/
|
||||
int movewconstspeed(int spd){
|
||||
if(chk_eswstates()) return 1;
|
||||
int16_t targspd = RAWSPEED(spd);
|
||||
unsigned char buf[8] = {0,};
|
||||
buf[1] = CW_ENABLE;
|
||||
@ -457,6 +503,7 @@ static int move(unsigned long targposition, int16_t rawspeed){
|
||||
verbose("Already at position\n");
|
||||
return 0;
|
||||
}
|
||||
if(chk_eswstates()) return 1;
|
||||
unsigned char buf[6] = {0,};
|
||||
DBG("Start moving with speed %d", rawspeed);
|
||||
buf[1] = CW_ENABLE;
|
||||
@ -481,25 +528,35 @@ static int move(unsigned long targposition, int16_t rawspeed){
|
||||
can_dsleep(0.01);
|
||||
//uint16_t motstat;
|
||||
double speed;
|
||||
eswstate esw;
|
||||
if(emerg_stop){ // emergency stop activated
|
||||
WARNX("Activated stop while moving");
|
||||
stop();
|
||||
curstatus = STAT_OK;
|
||||
return 1;
|
||||
}
|
||||
if(get_motor_speed(&speed) != CAN_NOERR){ // WTF?
|
||||
WARNX("Unknown situation: can't get speed of moving motor");
|
||||
stop();
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
getPos(NULL);
|
||||
if(curstatus != STAT_OK){
|
||||
WARNX("Something bad with end-switches");
|
||||
stop();
|
||||
return 1;
|
||||
}
|
||||
/* eswstate esw;
|
||||
if(get_endswitches(&esw) != CAN_NOERR){
|
||||
WARNX("Can't get endswitches state, stopping");
|
||||
stop();
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
if(esw != ESW_INACTIVE){ // OOps, something wrong
|
||||
if(esw == ESW_BOTH_ACTIVE){ // error!
|
||||
WARNX("Check end-switches, both active!");
|
||||
curstatus = STAT_BOTHESW;
|
||||
stop();
|
||||
return 1;
|
||||
}
|
||||
@ -507,20 +564,23 @@ static int move(unsigned long targposition, int16_t rawspeed){
|
||||
if(rawspeed > 0){ // moving CW, don't care for CCW esw state
|
||||
if(esw == ESW_CW_ACTIVE){
|
||||
WARNX("CW mowing: end-switch!");
|
||||
curstatus = STAT_ESW;
|
||||
stop();
|
||||
return 1;
|
||||
}
|
||||
}else{ // movig CCW
|
||||
if(esw == ESW_CCW_ACTIVE){
|
||||
WARNX("CCW mowing: end-switch!");
|
||||
curstatus = STAT_ESW;
|
||||
stop();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if(fabs(speed) < 0.1){ // || (motstat & (SW_B_UNBLOCK|SW_B_READY|SW_B_POUNBLOCK)) != (SW_B_UNBLOCK|SW_B_READY|SW_B_POUNBLOCK)){
|
||||
if(++zerctr == 10){
|
||||
WARNX("Motor stopped while moving!");
|
||||
curstatus = STAT_ERROR;
|
||||
stop();
|
||||
return 1;
|
||||
}
|
||||
@ -538,6 +598,7 @@ static int move(unsigned long targposition, int16_t rawspeed){
|
||||
if(i == MOVING_TIMEOUT){
|
||||
WARNX("Error: timeout, but motor still not @ position! STOP!");
|
||||
stop();
|
||||
curstatus = STAT_ERROR;
|
||||
return 1;
|
||||
}
|
||||
DBG("end-> curpos: %ld, difference: %ld, tm=%g\n", curposition, targposition - curposition, can_dtime()-t0);
|
||||
@ -553,6 +614,7 @@ static int move(unsigned long targposition, int16_t rawspeed){
|
||||
if(abs(targposition - curposition) > RAWPOS_TOLERANCE)
|
||||
verbose("Current (%ld) position is too far from target (%ld)\n", curposition, targposition);
|
||||
DBG("stop-> curpos: %ld, difference: %ld, tm=%g\n", curposition, targposition - curposition, can_dtime()-t0);
|
||||
curstatus = STAT_OK;
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -714,3 +776,7 @@ void movewithmon(double spd){
|
||||
}
|
||||
#undef PRINT
|
||||
}
|
||||
|
||||
sysstatus get_status(){
|
||||
return curstatus;
|
||||
}
|
||||
|
||||
@ -37,6 +37,18 @@ typedef enum{
|
||||
ESW_BOTH_ACTIVE = 3
|
||||
} eswstate;
|
||||
|
||||
// system state
|
||||
typedef enum{
|
||||
// non-blocking statuses:
|
||||
STAT_OK, // all OK
|
||||
// blocking statuses:
|
||||
STAT_ESW, // end-switch active
|
||||
STAT_BADESW, // wrong end-switch active
|
||||
STAT_BOTHESW, // both end-switches active
|
||||
STAT_GOFROMESW, // mowing from end-switch
|
||||
STAT_ERROR // uncoverable error
|
||||
} sysstatus;
|
||||
|
||||
int init_encoder(int encnode, int reset);
|
||||
void returnPreOper();
|
||||
int getPos(double *pos);
|
||||
@ -49,5 +61,6 @@ int move2pos(double target);
|
||||
int stop();
|
||||
int movewconstspeed(int spd);
|
||||
int go_out_from_ESW();
|
||||
sysstatus get_status();
|
||||
|
||||
#endif // CAN_ENCODER_H__
|
||||
|
||||
49
Z1000_focus/html/focus.html
Normal file
49
Z1000_focus/html/focus.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body{text-align:center;}
|
||||
.shadow{
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
vertical-align: center;
|
||||
top:0;
|
||||
display: none;
|
||||
left:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: lightGrey;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.B{font-weight: bold;}
|
||||
.big{font-size: 200%;}
|
||||
.M{margin-bottom: 5px;}
|
||||
.C{text-align:center; padding: 10px;}
|
||||
.btm{margin-top: 15px; color: red; position: fixed; bottom: 10px;
|
||||
width: 50%; left: 25%; font-size: 200%;}
|
||||
</style>
|
||||
<script src="focus.js" type="text/javascript" language="javascript"></script>
|
||||
<title>Zeiss-1000 focusing</title>
|
||||
</head>
|
||||
<body onload="Foc.Run();">
|
||||
<noscript><h1>Turn ON JavaScript!!!</h1></noscript>
|
||||
<div class="C" id="container">
|
||||
<div class="C M big" onclick="Foc.upd();">
|
||||
Current F=<span id="curFval"></span>
|
||||
</div>
|
||||
<div class="C M big">
|
||||
<label for="speed">Speed: </label>
|
||||
<input style="width: 50px" id="speed" type="number" value="1" min="1" max="4" onchange="Foc.chSpd(this.value);">
|
||||
<button class="B" id="F-" onmousedown="Foc.Move(-1);" onmouseup="Foc.Stop();">-</button>
|
||||
<!--<input style="width: 300px" id="focSlider" type="range" min="0" max="100" step="1" value="50" readonly>-->
|
||||
<button class="B" id="F+" onmousedown="Foc.Move(1);" onmouseup="Foc.Stop();">+</button>
|
||||
</div>
|
||||
<div class="C big">
|
||||
<button class="B" id="Fstop" onclick="Foc.Stop();">Stop</button>
|
||||
<input style="width: 130px" id="focSet" type="number" lang="en-150" value="2" min="0.1" max="65" step="0.01"> <!-- onchange="Foc.Ch(this.value);"-->
|
||||
<button class="B" id="Fset" onclick="Foc.SetFocus();">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shadow" id="shadow">
|
||||
</body>
|
||||
</html>
|
||||
143
Z1000_focus/html/focus.js
Normal file
143
Z1000_focus/html/focus.js
Normal file
@ -0,0 +1,143 @@
|
||||
Foc = function(){
|
||||
// const REQ_PATH=window.location.hostname+":4444/";
|
||||
const REQ_PATH="http://dasha.sao.ru:4444/";
|
||||
var minVal=0.01, maxVal=76.5, curVal = 3.0, curSpeed = 1;
|
||||
var timeout_upd, timeout_msg;
|
||||
// ID
|
||||
function $(id){ return document.getElementById(id); }
|
||||
// Request: req_SRT - request string, _onOK - function to run if all OK
|
||||
function sendrequest(req_STR, _onOK){
|
||||
var request = new XMLHttpRequest(), timeout_id;
|
||||
request.open("POST", REQ_PATH + req_STR, true);
|
||||
request.onreadystatechange=function(){
|
||||
if (request.readyState == 4){
|
||||
if (request.status == 200){
|
||||
clearTimeout(timeout_id);
|
||||
if(_onOK) _onOK(request);
|
||||
}
|
||||
else{
|
||||
clearTimeout(timeout_id);
|
||||
blockMsg("Request Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
request.send(req_STR);
|
||||
timeout_id = setTimeout(function(){blockMsg("Request timeout");}, 3000);
|
||||
}
|
||||
// show message blocking all
|
||||
function blockMsg(txt, bgcolor){
|
||||
$("shadow").style.display = "block";
|
||||
if(!bgcolor) bgcolor = "red";
|
||||
$("shadow").innerHTML = txt.replace("\n", "<br>");
|
||||
}
|
||||
// parse answer for status request
|
||||
function chkStatus(req){
|
||||
var msg = req.responseText;
|
||||
console.log("Get status message: " + msg);
|
||||
if(msg == "OK"){
|
||||
$("shadow").innerHTML = "";
|
||||
$("shadow").style.display = "none";
|
||||
}else blockMsg(msg);
|
||||
};
|
||||
// parse answer for command requests
|
||||
function chkCmd(req){
|
||||
var msg = req.responseText;
|
||||
console.log("Get cmd answer: " + msg);
|
||||
if(msg != "OK") alert(msg);
|
||||
}
|
||||
/*
|
||||
function ch_status(txt, bgcolor){
|
||||
function rmmsg(){clearTimeout(timeout_msg);document.body.removeChild($("_msg_div"));}
|
||||
clearTimeout(timeout_msg);
|
||||
if(!bgcolor) bgcolor = "red";
|
||||
if(!$("_msg_div")){ // ÄÏÂÁ×ÌÑÅÍ ÂÌÏË ÄÌÑ ×Ù×ÏÄÁ ÓÏÏÂÝÅÎÉÊ
|
||||
var div = document.createElement('div'), s = div.style;
|
||||
div.id = "_msg_div";
|
||||
div.class = "btm";
|
||||
document.body.appendChild(div);
|
||||
};
|
||||
$("_msg_div").style.backgroundColor = bgcolor;
|
||||
$("_msg_div").innerHTML = txt.replace("\n", "<br>");
|
||||
$("_msg_div").addEventListener("click", rmmsg, true)
|
||||
timeout_msg = setTimeout(rmmsg, 5000);
|
||||
}*/
|
||||
var first = true;
|
||||
function chF(req){
|
||||
console.log(req.responseText);
|
||||
curVal = Number(req.responseText);
|
||||
if(first){
|
||||
$('focSet').value = curVal;
|
||||
first = false;
|
||||
}
|
||||
$('curFval').innerHTML = curVal;
|
||||
//$('focSlider').value = curVal;
|
||||
}
|
||||
function getdata(){
|
||||
clearTimeout(timeout_upd);
|
||||
sendrequest("focus", chF);
|
||||
sendrequest("status", chkStatus);
|
||||
timeout_upd = setTimeout(getdata, 1000);
|
||||
}
|
||||
|
||||
// init all things
|
||||
function FirstRun(){
|
||||
blockMsg("init", "black");
|
||||
// first we should get all params
|
||||
chSpd($('speed').value);
|
||||
var F = $('focSet');
|
||||
F.value = curVal;
|
||||
F.min = minVal;
|
||||
F.max = maxVal;
|
||||
getdata();
|
||||
}
|
||||
// send new focus value
|
||||
function SetFocus(){
|
||||
var set = $('focSet').value;
|
||||
if(set < minVal || set > maxVal){
|
||||
alert("Wrong focus value");
|
||||
return;
|
||||
}
|
||||
console.log("Set focus: " + set);
|
||||
sendrequest("goto="+set, chkCmd);
|
||||
}
|
||||
function Move(dir){
|
||||
console.log("Move to " + ((dir > 0) ? "+" : "-") + " with speed " + curSpeed);
|
||||
var targspeeds = [ 130, 400, 800, 1200 ];
|
||||
var cmd = "targspeed=" + ((dir > 0) ? "" : "-") + targspeeds[curSpeed-1];
|
||||
sendrequest(cmd, chkCmd);
|
||||
console.log("send request " + cmd);
|
||||
}
|
||||
function Stop(){
|
||||
sendrequest("stop", chkCmd);
|
||||
console.log("Stop");
|
||||
}
|
||||
// slider or input field changed
|
||||
function change(val){
|
||||
if(val < minVal) val = minVal;
|
||||
else if(val > maxVal) val = maxVal;
|
||||
//$('focSlider').value = val;
|
||||
$('focSet').value = val;
|
||||
console.log("Chfocval: " + val);
|
||||
}
|
||||
function chSpd(val){
|
||||
if(val < 1) val = 1;
|
||||
else if(val > 4) val = 4;
|
||||
$('speed').value = val;
|
||||
curSpeed = val;
|
||||
console.log("Chspd: " + val);
|
||||
}
|
||||
// update value in input field by current
|
||||
function update(){
|
||||
$('focSet').value = curVal;
|
||||
}
|
||||
|
||||
return{
|
||||
Run: FirstRun,
|
||||
SetFocus: SetFocus,
|
||||
Move: Move,
|
||||
Stop: Stop,
|
||||
Ch: change,
|
||||
chSpd: chSpd,
|
||||
upd: update,
|
||||
}
|
||||
}();
|
||||
@ -193,7 +193,7 @@ static void *handle_socket(void *asock){
|
||||
// empty request == focus request
|
||||
if(strlen(found) < 1 || getparam(S_CMD_FOCUS)){
|
||||
DBG("position request");
|
||||
snprintf(buff, BUFLEN, "%.3f", curPos());
|
||||
snprintf(buff, BUFLEN, "%.03f", curPos());
|
||||
}else if(getparam(S_CMD_STOP)){
|
||||
DBG("Stop request");
|
||||
emerg_stop = TRUE;
|
||||
@ -226,6 +226,29 @@ static void *handle_socket(void *asock){
|
||||
DBG("Move to position %g request", pos);
|
||||
sprintf(buff, startmoving(pos));
|
||||
}
|
||||
}else if(getparam(S_CMD_STATUS)){
|
||||
const char *msg = S_STATUS_ERROR;
|
||||
switch(get_status()){
|
||||
case STAT_OK:
|
||||
msg = S_STATUS_OK;
|
||||
break;
|
||||
case STAT_BADESW:
|
||||
msg = S_STATUS_BADESW;
|
||||
break;
|
||||
case STAT_BOTHESW:
|
||||
msg = S_STATUS_BOTHESW;
|
||||
break;
|
||||
case STAT_ERROR:
|
||||
msg = S_STATUS_ERROR;
|
||||
break;
|
||||
case STAT_ESW:
|
||||
msg = S_STATUS_ESW;
|
||||
break;
|
||||
case STAT_GOFROMESW:
|
||||
msg = S_STATUS_GOFROMESW;
|
||||
break;
|
||||
}
|
||||
sprintf(buff, msg);
|
||||
}else sprintf(buff, S_ANS_ERR);
|
||||
if(!send_data(sock, webquery, buff)){
|
||||
WARNX("can't send data, some error occured");
|
||||
@ -297,9 +320,16 @@ static void daemon_(int sock){
|
||||
}
|
||||
usleep(500000); // sleep a little or thread's won't be able to lock mutex
|
||||
// get current position
|
||||
pthread_mutex_lock(&canbus_mutex);
|
||||
if(!pthread_mutex_trylock(&canbus_mutex)){
|
||||
getPos(NULL);
|
||||
if(get_status() != STAT_OK){
|
||||
if(!pthread_mutex_trylock(&moving_mutex)){
|
||||
go_out_from_ESW();
|
||||
pthread_mutex_unlock(&moving_mutex);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&canbus_mutex);
|
||||
}
|
||||
}while(1);
|
||||
putlog("daemon_(): UNREACHABLE CODE REACHED!");
|
||||
}
|
||||
|
||||
@ -36,12 +36,22 @@
|
||||
#define S_CMD_FOCUS "focus"
|
||||
#define S_CMD_TARGSPEED "targspeed"
|
||||
#define S_CMD_GOTO "goto"
|
||||
#define S_CMD_STATUS "status"
|
||||
|
||||
// answers through the socket
|
||||
#define S_ANS_ERR "error"
|
||||
#define S_ANS_OK "OK"
|
||||
#define S_ANS_MOVING "moving"
|
||||
|
||||
// statuses
|
||||
#define S_STATUS_OK "OK"
|
||||
#define S_STATUS_ESW "End-switch active"
|
||||
#define S_STATUS_ERROR "Uncoverable error"
|
||||
#define S_STATUS_BOTHESW "Both end-switches active"
|
||||
#define S_STATUS_BADESW "Wrong end-switch active"
|
||||
#define S_STATUS_ESW "End-switch active"
|
||||
#define S_STATUS_GOFROMESW "Moving from end-switch"
|
||||
|
||||
bool emerg_stop;
|
||||
|
||||
void daemonize(const char *port);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user