feat(pps): preserve loop dynamics across nav periods
This commit is contained in:
@@ -1844,7 +1844,9 @@ void gps_startup_initialization(unsigned int nsp_tick)
|
||||
GPS_NAV_ENABLE_FDE);
|
||||
gps_init_acq_tables();
|
||||
gps_pps_init(nsp_tick);
|
||||
gps_pps_quality_selector_init(&gps_pps_quality_selector);
|
||||
gps_pps_quality_selector_init_period(
|
||||
&gps_pps_quality_selector,
|
||||
(double)nsp_tick * (double)GPS_NAV_SOLVE_TIMER_TICK_MS / 1000.0);
|
||||
gps_pps_applied_profile = GPS_PPS_PROFILE_HOLDOVER;
|
||||
gps_pps_status_log_decimator = 0u;
|
||||
gps_pps_reference_svid_valid = false;
|
||||
|
||||
@@ -65,7 +65,8 @@ enum {
|
||||
};
|
||||
|
||||
#define GPS_PPS_VERSION_ADAPTIVE_LOOP 0x00020200u
|
||||
#define GPS_PPS_REFERENCE_TIMEOUT_TICKS (2u * GPS_PPS_CLOCK_HZ)
|
||||
#define GPS_PPS_MIN_REFERENCE_TIMEOUT_TICKS (2u * GPS_PPS_CLOCK_HZ)
|
||||
#define GPS_PPS_REFERENCE_TIMEOUT_PERIODS 3u
|
||||
|
||||
static volatile gps_pps_regs_t* const gps_pps =
|
||||
(volatile gps_pps_regs_t*)BSP_GPS_PPS_BASE;
|
||||
@@ -77,6 +78,7 @@ static bool gps_pps_force_coarse;
|
||||
static bool gps_pps_reference_committed;
|
||||
static bool gps_pps_holdover;
|
||||
static uint64_t gps_pps_last_reference_counter;
|
||||
static uint64_t gps_pps_reference_timeout_ticks;
|
||||
static gps_pps_time_filter_t gps_pps_time_filter;
|
||||
|
||||
static uint64_t gps_pps_read_u64(
|
||||
@@ -94,19 +96,30 @@ static void gps_pps_write_q46(volatile uint32_t* lo, volatile uint32_t* hi, int6
|
||||
*hi = (uint32_t)(encoded >> 32);
|
||||
}
|
||||
|
||||
static void gps_pps_write_loop_profile(gps_pps_profile_t profile)
|
||||
static double gps_pps_update_period_sec(void)
|
||||
{
|
||||
const double pi = 3.14159265358979323846;
|
||||
gps_pps_loop_profile_t parameters = gps_pps_loop_profile(profile);
|
||||
const double update_period_sec = (gps_pps_nav_solve_tick != 0u) ?
|
||||
((double)gps_pps_nav_solve_tick * 0.020) : 0.200;
|
||||
const double wn = 2.0 * pi * parameters.natural_frequency_hz;
|
||||
const double q46 = 70368744177664.0;
|
||||
int64_t kp_q46 = (int64_t)llround(
|
||||
(2.0 * parameters.damping * wn / GPS_PPS_CLOCK_HZ) * q46);
|
||||
int64_t ki_q46 = (int64_t)llround((wn * wn * update_period_sec / GPS_PPS_CLOCK_HZ) * q46);
|
||||
uint32_t max_slew_q32 = (uint32_t)llround(
|
||||
parameters.max_slew_ppm * 1.0e-6 * 4294967296.0);
|
||||
return (gps_pps_nav_solve_tick != 0u) ?
|
||||
((double)gps_pps_nav_solve_tick * 0.020) :
|
||||
GPS_PPS_BASELINE_UPDATE_PERIOD_SEC;
|
||||
}
|
||||
|
||||
static bool gps_pps_write_loop_profile(gps_pps_profile_t profile)
|
||||
{
|
||||
const double update_period_sec = gps_pps_update_period_sec();
|
||||
gps_pps_loop_profile_t parameters =
|
||||
gps_pps_loop_profile_for_period(profile, update_period_sec);
|
||||
int64_t kp_q46 = 0;
|
||||
int64_t ki_q46 = 0;
|
||||
uint32_t max_slew_q32 = 0u;
|
||||
if(!gps_pps_loop_coefficients(
|
||||
parameters, update_period_sec, (double)GPS_PPS_CLOCK_HZ,
|
||||
&kp_q46, &ki_q46, &max_slew_q32))
|
||||
{
|
||||
GPS_LOG(DEBUG_ERROR, GPS_LOG_CAT_NAV,
|
||||
"PPS loop coefficient generation failed profile=%s period_ms=%.1lf\r\n",
|
||||
gps_pps_profile_name(profile), update_period_sec * 1000.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
gps_pps_write_q46(&gps_pps->kp_q46_lo, &gps_pps->kp_q46_hi, kp_q46);
|
||||
gps_pps_write_q46(&gps_pps->ki_q46_lo, &gps_pps->ki_q46_hi, ki_q46);
|
||||
@@ -118,14 +131,25 @@ static void gps_pps_write_loop_profile(gps_pps_profile_t profile)
|
||||
((parameters.lock_release_ticks & 0xffu) << 16) |
|
||||
((parameters.lock_acquire_updates & 0xffu) << 24);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void gps_pps_init(unsigned int nav_solve_tick)
|
||||
{
|
||||
gps_pps_nav_solve_tick = nav_solve_tick;
|
||||
uint64_t period_ticks =
|
||||
((uint64_t)GPS_PPS_CLOCK_HZ * nav_solve_tick * 20u) / 1000u;
|
||||
gps_pps_reference_timeout_ticks =
|
||||
GPS_PPS_REFERENCE_TIMEOUT_PERIODS * period_ticks;
|
||||
if(gps_pps_reference_timeout_ticks <
|
||||
(uint64_t)GPS_PPS_MIN_REFERENCE_TIMEOUT_TICKS)
|
||||
{
|
||||
gps_pps_reference_timeout_ticks =
|
||||
GPS_PPS_MIN_REFERENCE_TIMEOUT_TICKS;
|
||||
}
|
||||
gps_pps->control = 0u;
|
||||
gps_pps_version = gps_pps->version;
|
||||
gps_pps_write_loop_profile(GPS_PPS_PROFILE_ROBUST);
|
||||
(void)gps_pps_write_loop_profile(GPS_PPS_PROFILE_ROBUST);
|
||||
gps_pps_active_loop_profile = GPS_PPS_PROFILE_ROBUST;
|
||||
/* A GDB CPU reset does not reset the tracking-clock PPS state. Continue
|
||||
* its mailbox sequence and force the first new solution to re-anchor the
|
||||
@@ -149,12 +173,14 @@ void gps_pps_init(unsigned int nav_solve_tick)
|
||||
GPS_PPS_CONTROL_CLEAR_STATUS;
|
||||
GPS_LOG(DEBUG_STATUS, GPS_LOG_CAT_NAV,
|
||||
"PPS init rtl=0x%08x adaptive=%u loop_config=0x%08x "
|
||||
"clock_hz=%u update_ticks=%u\r\n",
|
||||
"clock_hz=%u update_ticks=%u reference_timeout_ms=%lu\r\n",
|
||||
gps_pps_version,
|
||||
gps_pps_version >= GPS_PPS_VERSION_ADAPTIVE_LOOP ? 1u : 0u,
|
||||
gps_pps_version >= GPS_PPS_VERSION_ADAPTIVE_LOOP ?
|
||||
gps_pps->loop_config : 0u,
|
||||
GPS_PPS_CLOCK_HZ, gps_pps_nav_solve_tick);
|
||||
GPS_PPS_CLOCK_HZ, gps_pps_nav_solve_tick,
|
||||
(unsigned long)((gps_pps_reference_timeout_ticks * 1000u) /
|
||||
GPS_PPS_CLOCK_HZ));
|
||||
}
|
||||
|
||||
void gps_pps_service(void)
|
||||
@@ -170,7 +196,7 @@ void gps_pps_service(void)
|
||||
uint32_t now_hi = gps_pps->raw_counter_hi;
|
||||
uint64_t now = ((uint64_t)now_hi << 32) | now_lo;
|
||||
if((now - gps_pps_last_reference_counter) >
|
||||
(uint64_t)GPS_PPS_REFERENCE_TIMEOUT_TICKS)
|
||||
gps_pps_reference_timeout_ticks)
|
||||
{
|
||||
gps_pps_set_holdover(true);
|
||||
}
|
||||
@@ -229,7 +255,10 @@ bool gps_pps_set_loop_profile(gps_pps_profile_t profile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gps_pps_write_loop_profile(profile);
|
||||
if(!gps_pps_write_loop_profile(profile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gps_pps_active_loop_profile = profile;
|
||||
return true;
|
||||
}
|
||||
@@ -277,7 +306,8 @@ gps_pps_time_filter_result_t gps_pps_filter_clock_bias(
|
||||
double* innovation_m_out)
|
||||
{
|
||||
gps_pps_loop_profile_t parameters =
|
||||
gps_pps_loop_profile(gps_pps_active_loop_profile);
|
||||
gps_pps_loop_profile_for_period(
|
||||
gps_pps_active_loop_profile, gps_pps_update_period_sec());
|
||||
return gps_pps_time_filter_update(
|
||||
&gps_pps_time_filter, epoch_counter, (double)GPS_PPS_CLOCK_HZ,
|
||||
measured_clock_bias_m, clock_drift_valid,
|
||||
|
||||
@@ -7,6 +7,33 @@
|
||||
|
||||
#define GPS_PPS_PROFILE_UPGRADE_UPDATES 8u
|
||||
#define GPS_PPS_PROFILE_DEGRADE_UPDATES 3u
|
||||
#define GPS_PPS_MIN_HYSTERESIS_UPDATES 2u
|
||||
#define GPS_PPS_MIN_LOCK_UPDATES 2u
|
||||
#define GPS_PPS_MIN_SETTLE_UPDATES 3u
|
||||
#define GPS_PPS_MAX_CONFIG_UPDATES 255u
|
||||
|
||||
static unsigned int gps_pps_period_updates(
|
||||
unsigned int baseline_updates,
|
||||
double update_period_sec,
|
||||
unsigned int minimum_updates)
|
||||
{
|
||||
if(!isfinite(update_period_sec) || (update_period_sec <= 0.0))
|
||||
{
|
||||
update_period_sec = GPS_PPS_BASELINE_UPDATE_PERIOD_SEC;
|
||||
}
|
||||
double duration_sec =
|
||||
(double)baseline_updates * GPS_PPS_BASELINE_UPDATE_PERIOD_SEC;
|
||||
double required = ceil(duration_sec / update_period_sec - 1.0e-12);
|
||||
if(required < (double)minimum_updates)
|
||||
{
|
||||
return minimum_updates;
|
||||
}
|
||||
if(required > (double)GPS_PPS_MAX_CONFIG_UPDATES)
|
||||
{
|
||||
return GPS_PPS_MAX_CONFIG_UPDATES;
|
||||
}
|
||||
return (unsigned int)required;
|
||||
}
|
||||
|
||||
static bool gps_pps_metrics_finite(const gps_pps_quality_metrics_t* metrics)
|
||||
{
|
||||
@@ -67,12 +94,26 @@ static gps_pps_profile_t gps_pps_classify_quality(
|
||||
}
|
||||
|
||||
void gps_pps_quality_selector_init(gps_pps_quality_selector_t* selector)
|
||||
{
|
||||
gps_pps_quality_selector_init_period(
|
||||
selector, GPS_PPS_BASELINE_UPDATE_PERIOD_SEC);
|
||||
}
|
||||
|
||||
void gps_pps_quality_selector_init_period(
|
||||
gps_pps_quality_selector_t* selector,
|
||||
double update_period_sec)
|
||||
{
|
||||
if(selector != NULL)
|
||||
{
|
||||
memset(selector, 0, sizeof(*selector));
|
||||
selector->profile = GPS_PPS_PROFILE_HOLDOVER;
|
||||
selector->candidate = GPS_PPS_PROFILE_HOLDOVER;
|
||||
selector->upgrade_updates = gps_pps_period_updates(
|
||||
GPS_PPS_PROFILE_UPGRADE_UPDATES, update_period_sec,
|
||||
GPS_PPS_MIN_HYSTERESIS_UPDATES);
|
||||
selector->degrade_updates = gps_pps_period_updates(
|
||||
GPS_PPS_PROFILE_DEGRADE_UPDATES, update_period_sec,
|
||||
GPS_PPS_MIN_HYSTERESIS_UPDATES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +152,8 @@ gps_pps_profile_t gps_pps_quality_selector_update(
|
||||
|
||||
unsigned int required_updates =
|
||||
(target > selector->profile) ?
|
||||
GPS_PPS_PROFILE_UPGRADE_UPDATES :
|
||||
GPS_PPS_PROFILE_DEGRADE_UPDATES;
|
||||
selector->upgrade_updates :
|
||||
selector->degrade_updates;
|
||||
if(selector->candidate_updates >= required_updates)
|
||||
{
|
||||
selector->profile = target;
|
||||
@@ -127,10 +168,19 @@ gps_pps_profile_t gps_pps_quality_selector_update(
|
||||
|
||||
gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile)
|
||||
{
|
||||
return gps_pps_loop_profile_for_period(
|
||||
profile, GPS_PPS_BASELINE_UPDATE_PERIOD_SEC);
|
||||
}
|
||||
|
||||
gps_pps_loop_profile_t gps_pps_loop_profile_for_period(
|
||||
gps_pps_profile_t profile,
|
||||
double update_period_sec)
|
||||
{
|
||||
gps_pps_loop_profile_t parameters;
|
||||
switch(profile)
|
||||
{
|
||||
case GPS_PPS_PROFILE_ACQUIRE:
|
||||
return (gps_pps_loop_profile_t){
|
||||
parameters = (gps_pps_loop_profile_t){
|
||||
.natural_frequency_hz = 0.120,
|
||||
.damping = 0.9,
|
||||
.max_slew_ppm = 20.0,
|
||||
@@ -140,8 +190,9 @@ gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile)
|
||||
.software_bias_gain = 1.0 / 16.0,
|
||||
.software_settle_updates = 12u
|
||||
};
|
||||
break;
|
||||
case GPS_PPS_PROFILE_PRECISION:
|
||||
return (gps_pps_loop_profile_t){
|
||||
parameters = (gps_pps_loop_profile_t){
|
||||
.natural_frequency_hz = 0.035,
|
||||
.damping = 0.9,
|
||||
.max_slew_ppm = 20.0,
|
||||
@@ -151,8 +202,9 @@ gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile)
|
||||
.software_bias_gain = 1.0 / 32.0,
|
||||
.software_settle_updates = 12u
|
||||
};
|
||||
break;
|
||||
case GPS_PPS_PROFILE_ROBUST:
|
||||
return (gps_pps_loop_profile_t){
|
||||
parameters = (gps_pps_loop_profile_t){
|
||||
.natural_frequency_hz = 0.012,
|
||||
.damping = 1.0,
|
||||
.max_slew_ppm = 20.0,
|
||||
@@ -162,9 +214,10 @@ gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile)
|
||||
.software_bias_gain = 1.0 / 32.0,
|
||||
.software_settle_updates = 16u
|
||||
};
|
||||
break;
|
||||
case GPS_PPS_PROFILE_HOLDOVER:
|
||||
default:
|
||||
return (gps_pps_loop_profile_t){
|
||||
parameters = (gps_pps_loop_profile_t){
|
||||
.natural_frequency_hz = 0.0,
|
||||
.damping = 1.0,
|
||||
.max_slew_ppm = 20.0,
|
||||
@@ -174,7 +227,106 @@ gps_pps_loop_profile_t gps_pps_loop_profile(gps_pps_profile_t profile)
|
||||
.software_bias_gain = 1.0 / 32.0,
|
||||
.software_settle_updates = 16u
|
||||
};
|
||||
break;
|
||||
}
|
||||
parameters.lock_acquire_updates = gps_pps_period_updates(
|
||||
parameters.lock_acquire_updates, update_period_sec,
|
||||
GPS_PPS_MIN_LOCK_UPDATES);
|
||||
parameters.software_settle_updates = gps_pps_period_updates(
|
||||
parameters.software_settle_updates, update_period_sec,
|
||||
GPS_PPS_MIN_SETTLE_UPDATES);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
bool gps_pps_loop_coefficients(
|
||||
gps_pps_loop_profile_t parameters,
|
||||
double update_period_sec,
|
||||
double clock_frequency_hz,
|
||||
int64_t* kp_q46_out,
|
||||
int64_t* ki_q46_out,
|
||||
uint32_t* max_slew_q32_out)
|
||||
{
|
||||
if((kp_q46_out == NULL) || (ki_q46_out == NULL) ||
|
||||
(max_slew_q32_out == NULL) ||
|
||||
!isfinite(update_period_sec) || (update_period_sec <= 0.0) ||
|
||||
!isfinite(clock_frequency_hz) || (clock_frequency_hz <= 0.0) ||
|
||||
!isfinite(parameters.natural_frequency_hz) ||
|
||||
(parameters.natural_frequency_hz < 0.0) ||
|
||||
!isfinite(parameters.damping) || (parameters.damping < 0.0) ||
|
||||
!isfinite(parameters.max_slew_ppm) ||
|
||||
(parameters.max_slew_ppm < 0.0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double pi = 3.14159265358979323846;
|
||||
const double q46 = 70368744177664.0;
|
||||
const double q32 = 4294967296.0;
|
||||
const double baseline_sec = GPS_PPS_BASELINE_UPDATE_PERIOD_SEC;
|
||||
const double wn = 2.0 * pi * parameters.natural_frequency_hz;
|
||||
/* Use the actual deployed Q2.46 baseline, not its ideal double value, so
|
||||
* the mapped poles are those of the proven fixed-point implementation. */
|
||||
const double baseline_kp =
|
||||
(double)llround(
|
||||
(2.0 * parameters.damping * wn / clock_frequency_hz) * q46) /
|
||||
q46;
|
||||
const double baseline_ki =
|
||||
(double)llround(
|
||||
(wn * wn * baseline_sec / clock_frequency_hz) * q46) /
|
||||
q46;
|
||||
const double baseline_ticks = clock_frequency_hz * baseline_sec;
|
||||
const double baseline_sum =
|
||||
2.0 - baseline_ticks * (baseline_kp + baseline_ki);
|
||||
const double baseline_product =
|
||||
1.0 - baseline_ticks * baseline_kp;
|
||||
const double discriminant =
|
||||
baseline_sum * baseline_sum - 4.0 * baseline_product;
|
||||
const double period_ratio = update_period_sec / baseline_sec;
|
||||
double target_sum = 0.0;
|
||||
double target_product = 0.0;
|
||||
|
||||
if(discriminant >= 0.0)
|
||||
{
|
||||
double root = sqrt(discriminant);
|
||||
double pole_a = 0.5 * (baseline_sum + root);
|
||||
double pole_b = 0.5 * (baseline_sum - root);
|
||||
if((pole_a < 0.0) || (pole_b < 0.0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
double target_a = pow(pole_a, period_ratio);
|
||||
double target_b = pow(pole_b, period_ratio);
|
||||
target_sum = target_a + target_b;
|
||||
target_product = target_a * target_b;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(baseline_product <= 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
double radius = sqrt(baseline_product);
|
||||
double cosine = baseline_sum / (2.0 * radius);
|
||||
cosine = fmax(-1.0, fmin(1.0, cosine));
|
||||
double angle = acos(cosine);
|
||||
double target_radius = pow(radius, period_ratio);
|
||||
target_sum =
|
||||
2.0 * target_radius * cos(angle * period_ratio);
|
||||
target_product = target_radius * target_radius;
|
||||
}
|
||||
|
||||
double target_ticks = clock_frequency_hz * update_period_sec;
|
||||
double kp = (1.0 - target_product) / target_ticks;
|
||||
double ki = (1.0 - target_sum + target_product) / target_ticks;
|
||||
if(!isfinite(kp) || !isfinite(ki) || (kp < 0.0) || (ki < 0.0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*kp_q46_out = (int64_t)llround(kp * q46);
|
||||
*ki_q46_out = (int64_t)llround(ki * q46);
|
||||
*max_slew_q32_out = (uint32_t)llround(
|
||||
parameters.max_slew_ppm * 1.0e-6 * q32);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* gps_pps_profile_name(gps_pps_profile_t profile)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define GPS_PPS_FILTER_INNOVATION_CLAMP_M 300.0
|
||||
#define GPS_PPS_FILTER_HARD_OUTLIER_M 1500.0
|
||||
#define GPS_PPS_SPEED_OF_LIGHT_MPS 299792458.0
|
||||
#define GPS_PPS_FILTER_BASELINE_PERIOD_SEC 0.200
|
||||
|
||||
static double clamp_double(double value, double limit)
|
||||
{
|
||||
@@ -34,6 +35,21 @@ static double clamp_double(double value, double limit)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Preserve the baseline discrete pole when the navigation interval changes.
|
||||
* Applying a baseline gain alpha N times is equivalent to one elapsed-time
|
||||
* update with alpha_T = 1 - (1 - alpha)^N. This also handles an occasional
|
||||
* missed solve without slowing the clock-bias or drift estimator. */
|
||||
static double gps_pps_filter_elapsed_gain(
|
||||
double baseline_gain, double elapsed_sec)
|
||||
{
|
||||
double periods = elapsed_sec / GPS_PPS_FILTER_BASELINE_PERIOD_SEC;
|
||||
if(fabs(periods - 1.0) <= 1.0e-12)
|
||||
{
|
||||
return baseline_gain;
|
||||
}
|
||||
return -expm1(periods * log1p(-baseline_gain));
|
||||
}
|
||||
|
||||
void gps_pps_time_filter_init(gps_pps_time_filter_t* filter)
|
||||
{
|
||||
if(filter != NULL)
|
||||
@@ -269,14 +285,20 @@ gps_pps_time_filter_result_t gps_pps_time_filter_update(
|
||||
return GPS_PPS_TIME_FILTER_REJECTED;
|
||||
}
|
||||
|
||||
double effective_bias_gain =
|
||||
gps_pps_filter_elapsed_gain(bias_gain, elapsed_sec);
|
||||
double effective_drift_gain = gps_pps_filter_elapsed_gain(
|
||||
GPS_PPS_FILTER_DRIFT_GAIN, elapsed_sec);
|
||||
double effective_fallback_drift_gain = gps_pps_filter_elapsed_gain(
|
||||
GPS_PPS_FILTER_FALLBACK_DRIFT_GAIN, elapsed_sec);
|
||||
filter->clock_bias_m =
|
||||
predicted_clock_bias_m +
|
||||
bias_gain *
|
||||
effective_bias_gain *
|
||||
clamp_double(innovation_m, GPS_PPS_FILTER_INNOVATION_CLAMP_M);
|
||||
if(clock_drift_valid)
|
||||
{
|
||||
filter->clock_drift_mps +=
|
||||
GPS_PPS_FILTER_DRIFT_GAIN *
|
||||
effective_drift_gain *
|
||||
(measured_clock_drift_mps - filter->clock_drift_mps);
|
||||
}
|
||||
else
|
||||
@@ -291,7 +313,7 @@ gps_pps_time_filter_result_t gps_pps_time_filter_update(
|
||||
else
|
||||
{
|
||||
filter->clock_drift_mps +=
|
||||
GPS_PPS_FILTER_FALLBACK_DRIFT_GAIN *
|
||||
effective_fallback_drift_gain *
|
||||
(fallback_drift_mps - filter->clock_drift_mps);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user