Start
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
from picamera import PiCamera
|
||||
from time import sleep
|
||||
'''
|
||||
This script helps to test the functionality of the Raspberry Pi camera.
|
||||
The script must be run dicrectly on the Raspberry Pi (not in the Docker container)
|
||||
'''
|
||||
camera = PiCamera()
|
||||
|
||||
camera.start_preview()
|
||||
sleep(5)
|
||||
camera.stop_preview()
|
||||
@@ -0,0 +1,96 @@
|
||||
import Adafruit_PCA9685
|
||||
import yaml
|
||||
import time
|
||||
import os
|
||||
|
||||
config_filename = '../config/exomy.yaml'
|
||||
|
||||
|
||||
def get_driving_pins():
|
||||
pin_list = []
|
||||
with open(config_filename, 'r') as file:
|
||||
param_dict = yaml.load(file)
|
||||
|
||||
for key, value in param_dict.items():
|
||||
if('pin_drive_' in str(key)):
|
||||
pin_list.append(value)
|
||||
return pin_list
|
||||
|
||||
def get_drive_pwm_neutral():
|
||||
|
||||
with open(config_filename, 'r') as file:
|
||||
param_dict = yaml.load(file)
|
||||
|
||||
for key, value in param_dict.items():
|
||||
if('drive_pwm_neutral' in str(key)):
|
||||
return value
|
||||
|
||||
default_value = 300
|
||||
print('The parameter drive_pwm_neutral could not be found in the exomy.yaml \n')
|
||||
print('It was set to the default value: '+ default_value + '\n')
|
||||
return default_value
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(
|
||||
'''
|
||||
$$$$$$$$\ $$\ $$\
|
||||
$$ _____| $$$\ $$$ |
|
||||
$$ | $$\ $$\ $$$$$$\ $$$$\ $$$$ |$$\ $$\
|
||||
$$$$$\ \$$\ $$ |$$ __$$\ $$\$$\$$ $$ |$$ | $$ |
|
||||
$$ __| \$$$$ / $$ / $$ |$$ \$$$ $$ |$$ | $$ |
|
||||
$$ | $$ $$< $$ | $$ |$$ |\$ /$$ |$$ | $$ |
|
||||
$$$$$$$$\ $$ /\$$\ \$$$$$$ |$$ | \_/ $$ |\$$$$$$$ |
|
||||
\________|\__/ \__| \______/ \__| \__| \____$$ |
|
||||
$$\ $$ |
|
||||
\$$$$$$ |
|
||||
\______/
|
||||
'''
|
||||
)
|
||||
print(
|
||||
'''
|
||||
This script helps you to set the neutral values of PWM of the driving motors correctly.
|
||||
It will send the intended signal for "not moving" to all the motors.
|
||||
On each motor you have to turn the correction screw until the motor really stands still.
|
||||
'''
|
||||
)
|
||||
|
||||
if not os.path.exists(config_filename):
|
||||
print("exomy.yaml does not exist. Finish config_motor_pins.py to generate it.")
|
||||
exit()
|
||||
|
||||
|
||||
pwm = Adafruit_PCA9685.PCA9685()
|
||||
|
||||
'''
|
||||
The drive_pwm_neutral value is determined from the exomy.yaml file.
|
||||
But it can be also calculated from the values of the PWM board and motors,
|
||||
like shown in the following calculation:
|
||||
|
||||
# For most motors a pwm frequency of 50Hz is normal
|
||||
pwm_frequency = 50.0 # Hz
|
||||
pwm.set_pwm_freq(pwm_frequency)
|
||||
|
||||
# The cycle is the inverted frequency converted to milliseconds
|
||||
cycle = 1.0/pwm_frequency * 1000.0 # 20 ms
|
||||
|
||||
# The time the pwm signal is set to on during the duty cycle
|
||||
on_time = 1.5 # ms
|
||||
|
||||
# Duty cycle is the percentage of a cycle the signal is on
|
||||
duty_cycle = on_time/cycle # 0.075
|
||||
|
||||
# The PCA 9685 board requests a 12 bit number for the duty_cycle
|
||||
value = int(duty_cycle*4096.0) # 307
|
||||
'''
|
||||
|
||||
value = get_drive_pwm_neutral()
|
||||
pin_list = get_driving_pins()
|
||||
|
||||
for pin in pin_list:
|
||||
pwm.set_pwm(pin, 0, value)
|
||||
time.sleep(0.1)
|
||||
|
||||
raw_input('Press any button if you are done to complete configuration\n')
|
||||
|
||||
for pin in pin_list:
|
||||
pwm.set_pwm(pin, 0, 0)
|
||||
@@ -0,0 +1,220 @@
|
||||
import Adafruit_PCA9685
|
||||
import time
|
||||
from shutil import copyfile
|
||||
import os
|
||||
|
||||
DRIVE_MOTOR, STEER_MOTOR = [0, 1]
|
||||
|
||||
pos_names = {
|
||||
1: 'fl',
|
||||
2: 'fr',
|
||||
3: 'cl',
|
||||
4: 'cr',
|
||||
5: 'rl',
|
||||
6: 'rr',
|
||||
}
|
||||
|
||||
pin_dict = {
|
||||
|
||||
}
|
||||
|
||||
pwm = Adafruit_PCA9685.PCA9685()
|
||||
# For most motors a pwm frequency of 50Hz is normal
|
||||
pwm_frequency = 50.0 # Hz
|
||||
pwm.set_pwm_freq(pwm_frequency)
|
||||
# The cycle is the inverted frequency converted to milliseconds
|
||||
cycle = 1.0/pwm_frequency * 1000.0 # ms
|
||||
|
||||
# The time the pwm signal is set to on during the duty cycle
|
||||
on_time_1 = 2.4 # ms
|
||||
on_time_2 = 1.5 # ms
|
||||
|
||||
# Duty cycle is the percentage of a cycle the signal is on
|
||||
duty_cycle_1 = on_time_1/cycle
|
||||
duty_cycle_2 = on_time_2/cycle
|
||||
|
||||
# The PCA 9685 board requests a 12 bit number for the duty_cycle
|
||||
value_1 = 200 #int(duty_cycle_1*4096.0)
|
||||
value_2 = 400#int(duty_cycle_2*4096.0)
|
||||
|
||||
class Motor():
|
||||
def __init__(self, pin):
|
||||
|
||||
self.pin_name = 'pin_'
|
||||
self.pin_number = pin
|
||||
|
||||
def wiggle_motor(self):
|
||||
|
||||
# Set the motor to the second value
|
||||
pwm.set_pwm(self.pin_number, 0, value_2)
|
||||
# Wait for 1 seconds
|
||||
time.sleep(1.0)
|
||||
# Set the motor to the first value
|
||||
pwm.set_pwm(self.pin_number, 0, value_1)
|
||||
# Wait for 1 seconds
|
||||
time.sleep(1.0)
|
||||
# Set the motor to neutral
|
||||
pwm.set_pwm(self.pin_number, 0, 307)
|
||||
# Wait for half seconds
|
||||
time.sleep(0.5)
|
||||
# Stop the motor
|
||||
pwm.set_pwm(self.pin_number, 0, 0)
|
||||
|
||||
def stop_motor(self):
|
||||
# Turn the motor off
|
||||
pwm.set_pwm(self.pin_number, 0, 0)
|
||||
|
||||
|
||||
def print_exomy_layout():
|
||||
print(
|
||||
'''
|
||||
1 fl-||-fr 2
|
||||
||
|
||||
3 cl-||-cr 4
|
||||
5 rl====rr 6
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
def update_config_file():
|
||||
file_name = '../config/exomy.yaml'
|
||||
template_file_name = file_name+'.template'
|
||||
|
||||
if not os.path.exists(file_name):
|
||||
copyfile(template_file_name, file_name)
|
||||
print("exomy.yaml.template was copied to exomy.yaml")
|
||||
|
||||
output = ''
|
||||
with open(file_name, 'rt') as file:
|
||||
for line in file:
|
||||
for key, value in pin_dict.items():
|
||||
if(key in line):
|
||||
line = line.replace(line.split(': ', 1)[
|
||||
1], str(value) + '\n')
|
||||
|
||||
break
|
||||
output += line
|
||||
|
||||
with open(file_name, 'w') as file:
|
||||
file.write(output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(
|
||||
'''
|
||||
$$$$$$$$\ $$\ $$\
|
||||
$$ _____| $$$\ $$$ |
|
||||
$$ | $$\ $$\ $$$$$$\ $$$$\ $$$$ |$$\ $$\
|
||||
$$$$$\ \$$\ $$ |$$ __$$\ $$\$$\$$ $$ |$$ | $$ |
|
||||
$$ __| \$$$$ / $$ / $$ |$$ \$$$ $$ |$$ | $$ |
|
||||
$$ | $$ $$< $$ | $$ |$$ |\$ /$$ |$$ | $$ |
|
||||
$$$$$$$$\ $$ /\$$\ \$$$$$$ |$$ | \_/ $$ |\$$$$$$$ |
|
||||
\________|\__/ \__| \______/ \__| \__| \____$$ |
|
||||
$$\ $$ |
|
||||
\$$$$$$ |
|
||||
\______/
|
||||
'''
|
||||
)
|
||||
print(
|
||||
'''
|
||||
###############
|
||||
Motor Configuration
|
||||
|
||||
This scripts leads you through the configuration of the motors.
|
||||
First we have to find out, to which pin of the PWM board a motor is connected.
|
||||
Look closely which motor moves and type in the answer.
|
||||
|
||||
Ensure to run the script until the end, otherwise your changes will not be saved!
|
||||
This script can always be stopped with ctrl+c and restarted.
|
||||
All other controls will be explained in the process.
|
||||
###############
|
||||
'''
|
||||
)
|
||||
|
||||
for pin_number in range(16):
|
||||
motor = Motor(pin_number)
|
||||
motor.stop_motor()
|
||||
|
||||
for pin_number in range(16):
|
||||
motor = Motor(pin_number)
|
||||
motor.wiggle_motor()
|
||||
type_selection = ''
|
||||
while(1):
|
||||
print("Pin #{}".format(pin_number))
|
||||
print(
|
||||
'Was it a steering or driving motor that moved, or should I repeat the movement? ')
|
||||
type_selection = raw_input('(d)rive (s)teer (r)epeat - (n)one (f)inish_configuration\n')
|
||||
if(type_selection == 'd'):
|
||||
motor.pin_name += 'drive_'
|
||||
print('Good job\n')
|
||||
break
|
||||
elif(type_selection == 's'):
|
||||
motor.pin_name += 'steer_'
|
||||
print('Good job\n')
|
||||
break
|
||||
elif(type_selection == 'r'):
|
||||
print('Look closely\n')
|
||||
motor.wiggle_motor()
|
||||
elif(type_selection == 'n'):
|
||||
print('Skipping pin')
|
||||
break
|
||||
elif(type_selection == 'f'):
|
||||
print('Finishing calibration at pin {}.'.format(pin_number))
|
||||
break
|
||||
else:
|
||||
print('Input must be d, s, r, n or f\n')
|
||||
|
||||
if (type_selection == 'd' or type_selection == 's'):
|
||||
while(1):
|
||||
print_exomy_layout()
|
||||
pos_selection = raw_input(
|
||||
'Type the position of the motor that moved.[1-6] or (r)epeat\n')
|
||||
if(pos_selection == 'r'):
|
||||
print('Look closely\n')
|
||||
else:
|
||||
try:
|
||||
pos = int(pos_selection)
|
||||
if(pos >= 1 and pos <= 6):
|
||||
motor.pin_name += pos_names[pos]
|
||||
break
|
||||
else:
|
||||
print('The input was not a number between 1 and 6\n')
|
||||
except ValueError:
|
||||
print('The input was not a number between 1 and 6\n')
|
||||
|
||||
pin_dict[motor.pin_name] = motor.pin_number
|
||||
print('Motor set!\n')
|
||||
print('########################################################\n')
|
||||
elif (type_selection == 'f'):
|
||||
break
|
||||
|
||||
|
||||
print('Now we will step through all the motors and check whether they have been assigned correctly.\n')
|
||||
print('Press ctrl+c if something is wrong and start the script again. \n')
|
||||
|
||||
for pin_name in pin_dict:
|
||||
print('moving {}'.format(pin_name))
|
||||
print_exomy_layout()
|
||||
|
||||
pin = pin_dict[pin_name]
|
||||
motor = Motor(pin)
|
||||
motor.wiggle_motor()
|
||||
raw_input('Press button to continue')
|
||||
|
||||
print("You assigned {}/12 motors.".format(len(pin_dict.keys())))
|
||||
|
||||
print('Write to config file.\n')
|
||||
update_config_file()
|
||||
print(
|
||||
'''
|
||||
$$$$$$$$\ $$\ $$\ $$\ $$\
|
||||
$$ _____|\__| \__| $$ | $$ |
|
||||
$$ | $$\ $$$$$$$\ $$\ $$$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$$ |
|
||||
$$$$$\ $$ |$$ __$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ $$ __$$ |
|
||||
$$ __| $$ |$$ | $$ |$$ |\$$$$$$\ $$ | $$ |$$$$$$$$ |$$ / $$ |
|
||||
$$ | $$ |$$ | $$ |$$ | \____$$\ $$ | $$ |$$ ____|$$ | $$ |
|
||||
$$ | $$ |$$ | $$ |$$ |$$$$$$$ |$$ | $$ |\$$$$$$$\ \$$$$$$$ |
|
||||
\__| \__|\__| \__|\__|\_______/ \__| \__| \_______| \_______|
|
||||
|
||||
''')
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import Adafruit_PCA9685
|
||||
import yaml
|
||||
import time
|
||||
import os
|
||||
|
||||
config_filename = '../config/exomy.yaml'
|
||||
|
||||
|
||||
def get_steering_motor_pins():
|
||||
steering_motor_pins = {}
|
||||
with open(config_filename, 'r') as file:
|
||||
param_dict = yaml.load(file)
|
||||
|
||||
for param_key, param_value in param_dict.items():
|
||||
if('pin_steer_' in str(param_key)):
|
||||
steering_motor_pins[param_key] = param_value
|
||||
return steering_motor_pins
|
||||
|
||||
def get_steering_pwm_neutral_values():
|
||||
steering_pwm_neutral_values = {}
|
||||
with open(config_filename, 'r') as file:
|
||||
param_dict = yaml.load(file)
|
||||
|
||||
for param_key, param_value in param_dict.items():
|
||||
if('steer_pwm_neutral_' in str(param_key)):
|
||||
steering_pwm_neutral_values[param_key] = param_value
|
||||
return steering_pwm_neutral_values
|
||||
|
||||
|
||||
def get_position_name(name):
|
||||
position_name = ''
|
||||
if('_fl' in name):
|
||||
position_name = 'Front Left'
|
||||
elif('_fr' in name):
|
||||
position_name = 'Front Right'
|
||||
elif('_cl' in name):
|
||||
position_name = 'Center Left'
|
||||
elif('_cr' in name):
|
||||
position_name = 'Center Right'
|
||||
elif('_rl' in name):
|
||||
position_name = 'Rear Left'
|
||||
elif('_rr' in name):
|
||||
position_name = 'Rear Right'
|
||||
|
||||
return position_name
|
||||
|
||||
|
||||
def update_config_file(steering_pwm_neutral_dict):
|
||||
output = ''
|
||||
with open(config_filename, 'rt') as file:
|
||||
for line in file:
|
||||
for key, value in steering_pwm_neutral_dict.items():
|
||||
if(key in line):
|
||||
line = line.replace(line.split(': ', 1)[
|
||||
1], str(value) + '\n')
|
||||
|
||||
break
|
||||
output += line
|
||||
|
||||
with open(config_filename, 'w') as file:
|
||||
file.write(output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(
|
||||
'''
|
||||
$$$$$$$$\ $$\ $$\
|
||||
$$ _____| $$$\ $$$ |
|
||||
$$ | $$\ $$\ $$$$$$\ $$$$\ $$$$ |$$\ $$\
|
||||
$$$$$\ \$$\ $$ |$$ __$$\ $$\$$\$$ $$ |$$ | $$ |
|
||||
$$ __| \$$$$ / $$ / $$ |$$ \$$$ $$ |$$ | $$ |
|
||||
$$ | $$ $$< $$ | $$ |$$ |\$ /$$ |$$ | $$ |
|
||||
$$$$$$$$\ $$ /\$$\ \$$$$$$ |$$ | \_/ $$ |\$$$$$$$ |
|
||||
\________|\__/ \__| \______/ \__| \__| \____$$ |
|
||||
$$\ $$ |
|
||||
\$$$$$$ |
|
||||
\______/
|
||||
'''
|
||||
)
|
||||
print(
|
||||
'''
|
||||
This script helps you to set the neutral pwm values for the steering motors.
|
||||
You will iterate over all steering motors and set them to a neutral position.
|
||||
The determined value is written to the config file.
|
||||
|
||||
Commands:
|
||||
a - Decrease value for current pin
|
||||
d - Increase value for current pin
|
||||
q - Finish setting value for current pin
|
||||
|
||||
[Every of these commands must be confirmed with the enter key]
|
||||
|
||||
ctrl+c - Exit script
|
||||
------------------------------------------------------------------------------
|
||||
'''
|
||||
)
|
||||
|
||||
if not os.path.exists(config_filename):
|
||||
print("exomy.yaml does not exist. Finish config_motor_pins.py to generate it.")
|
||||
exit()
|
||||
|
||||
pwm = Adafruit_PCA9685.PCA9685()
|
||||
# For most motors a pwm frequency of 50Hz is normal
|
||||
pwm_frequency = 50.0 # Hz
|
||||
pwm.set_pwm_freq(pwm_frequency)
|
||||
|
||||
# The cycle is the inverted frequency converted to milliseconds
|
||||
cycle = 1.0/pwm_frequency * 1000.0 # ms
|
||||
|
||||
# The time the pwm signal is set to on during the duty cycle
|
||||
on_time = 1.5 # ms
|
||||
|
||||
# Duty cycle is the percentage of a cycle the signal is on
|
||||
duty_cycle = on_time/cycle
|
||||
|
||||
# The PCA 9685 board requests a 12 bit number for the duty_cycle
|
||||
initial_value = int(duty_cycle*4096.0)
|
||||
|
||||
# Get all steering pins
|
||||
steering_motor_pins = get_steering_motor_pins()
|
||||
pwm_neutral_dict = get_steering_pwm_neutral_values()
|
||||
# Iterating over all motors and fine tune the zero value
|
||||
for pin_name, pin_value in steering_motor_pins.items():
|
||||
pwm_neutral_name = pin_name.replace('pin_steer_', 'steer_pwm_neutral_')
|
||||
pwm_neutral_value = pwm_neutral_dict[pwm_neutral_name]
|
||||
|
||||
print('Set ' + get_position_name(pin_name) + ' steering motor: \n')
|
||||
while(1):
|
||||
# Set motor
|
||||
pwm.set_pwm(pin_value, 0, pwm_neutral_value)
|
||||
time.sleep(0.1)
|
||||
print('Current value: ' + str(pwm_neutral_value) + '\n')
|
||||
input = raw_input(
|
||||
'q-set / a-decrease pwm neutral value/ d-increase pwm neutral value\n')
|
||||
if(input is 'q'):
|
||||
print('PWM neutral value for ' + get_position_name(pin_name) +
|
||||
' has been set.\n')
|
||||
break
|
||||
elif(input is 'a'):
|
||||
print('Decreased pwm neutral value')
|
||||
pwm_neutral_value-= 5
|
||||
elif(input is 'd'):
|
||||
print('Increased pwm neutral value')
|
||||
pwm_neutral_value += 5
|
||||
pwm_neutral_dict[pwm_neutral_name] = pwm_neutral_value
|
||||
update_config_file(pwm_neutral_dict)
|
||||
print("Finished configuration!!!")
|
||||
@@ -0,0 +1,94 @@
|
||||
import Adafruit_PCA9685
|
||||
import time
|
||||
import sys
|
||||
'''
|
||||
This script helps to test pwm motors with the Adafruit PCA9685 board
|
||||
Example usage:
|
||||
python motor_test.py 3
|
||||
|
||||
Performs a motor test for the motor connected to pin 3 of the PWM board
|
||||
'''
|
||||
# Check if the pin number is given as an argument
|
||||
if len(sys.argv) < 2:
|
||||
print('You must give the pin number of the motor to be tested as argument.')
|
||||
print('E.g: python motor_test.py 3')
|
||||
print('Tests the motor connected to pin 3.')
|
||||
exit()
|
||||
|
||||
# Set the pin of the motor
|
||||
pin = int(sys.argv[1])
|
||||
print('Pin: '+str(pin))
|
||||
|
||||
|
||||
pwm = Adafruit_PCA9685.PCA9685()
|
||||
# For most motors a pwm frequency of 50Hz is normal
|
||||
pwm_frequency = 50.0 #Hz
|
||||
pwm.set_pwm_freq(pwm_frequency)
|
||||
|
||||
# The cycle is the inverted frequency converted to milliseconds
|
||||
cycle = 1.0/pwm_frequency * 1000.0 #ms
|
||||
|
||||
# The time the pwm signal is set to on during the duty cycle
|
||||
on_time = 2.0 #ms
|
||||
|
||||
selection = ''
|
||||
|
||||
while(selection != '0'):
|
||||
|
||||
print("What do you want to test?")
|
||||
print("1. Min to Max oscilation")
|
||||
print('2. Incremental positioning')
|
||||
print('0. Abort')
|
||||
selection = raw_input()
|
||||
|
||||
if (int(selection) == 1):
|
||||
|
||||
min_t = 0.5 # ms
|
||||
max_t = 2.5 # ms
|
||||
mid_t = min_t+max_t/2
|
||||
|
||||
print("pulsewidth_min = {:.2f}, pulsewidth_max = {:.2f}".format(min_t, max_t))
|
||||
|
||||
# *_dc is the percentage of a cycle the signal is on
|
||||
min_dc = min_t/cycle
|
||||
max_dc = max_t/cycle
|
||||
mid_dc = mid_t/cycle
|
||||
|
||||
dc_list = [min_dc, mid_dc, max_dc, mid_dc]
|
||||
for dc in dc_list:
|
||||
pwm.set_pwm(pin, 0, int(dc*4096.0))
|
||||
time.sleep(2.0)
|
||||
|
||||
if (int(selection) == 2):
|
||||
|
||||
curr_t = 1.5 # ms
|
||||
curr_dc = curr_t/cycle
|
||||
|
||||
step_size = 0.1 # ms
|
||||
|
||||
step_size_scaling = 0.2
|
||||
|
||||
dc_selection = ''
|
||||
|
||||
while (dc_selection != '0'):
|
||||
dc_selection = raw_input('a-d: change pulsewidth | w-s: change step size | 0: back to menu\n')
|
||||
if dc_selection == 'a':
|
||||
curr_t = curr_t - step_size
|
||||
elif dc_selection == 'd':
|
||||
curr_t = curr_t + step_size
|
||||
elif dc_selection == 's':
|
||||
step_size = step_size*(1-step_size_scaling)
|
||||
elif dc_selection == 'w':
|
||||
step_size = step_size*(1+step_size_scaling)
|
||||
|
||||
curr_dc = curr_t/cycle
|
||||
|
||||
curr_pwm = int(curr_dc*4096.0)
|
||||
print("t_current:\t{0:.4f} [ms]\nstep_size:\t{1:.4f} [ms]\ncurr_pwm: {2:.2f}".format(curr_t, step_size, curr_pwm))
|
||||
|
||||
pwm.set_pwm(pin, 0, curr_pwm)
|
||||
|
||||
|
||||
|
||||
# The PCA 9685 board requests a 12 bit number for the duty_cycle
|
||||
pwm.set_pwm(pin, 0, 0)
|
||||
@@ -0,0 +1,14 @@
|
||||
import Adafruit_PCA9685
|
||||
import time
|
||||
import sys
|
||||
|
||||
'''
|
||||
This script simply stops all the motors, in case they were left in a running state.
|
||||
'''
|
||||
pwm = Adafruit_PCA9685.PCA9685()
|
||||
# For most motors a pwm frequency of 50Hz is normal
|
||||
pwm_frequency = 50.0 # Hz
|
||||
pwm.set_pwm_freq(pwm_frequency)
|
||||
|
||||
for pin_number in range(16):
|
||||
pwm.set_pwm(pin_number, 0, 0)
|
||||
Reference in New Issue
Block a user