first commit
This commit is contained in:
commit
1b5b613175
31
README.md
Executable file
31
README.md
Executable file
|
@ -0,0 +1,31 @@
|
|||
Telecommande Domotique
|
||||
===========================
|
||||
<img src="http://xavier.debert.free.fr/telecom/relais.jpg" width="50%"><img src="http://xavier.debert.free.fr/telecom/schema_funduino_keyes.jpg" width="50%"><img src="http://xavier.debert.free.fr/telecom/funduino_keyes_8_prises.jpg" width="50%">
|
||||
## Pre-requis
|
||||
|
||||
sudo apt-get install rpi.gpio
|
||||
|
||||
ou en manuel
|
||||
|
||||
wget https://pypi.python.org/packages/source/R/RPi.GPIO/RPi.GPIO-0.5.11.tar.gz
|
||||
tar -xvf RPi.GPIO-0.5.11.tar.gz
|
||||
cd RPi.GPIO-0.5.11
|
||||
sudo python setup.py install
|
||||
|
||||
## 1) Installation
|
||||
|
||||
dans /home/pi
|
||||
des fichiers index.html gohttp.py et relay.py et favicon.ico
|
||||
|
||||
## 2) demarrage auto
|
||||
|
||||
editer dans /etc
|
||||
le rc.local
|
||||
ajouter en fin avant exit
|
||||
|
||||
#gestion Relay a 0
|
||||
./home/pi/relay.py power 1
|
||||
gohttp.sh
|
||||
|
||||
faire un reboot
|
||||
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
203
gohttp.py
Executable file
203
gohttp.py
Executable file
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Very simple HTTP server in python for logging requests
|
||||
Usage::
|
||||
./server.py [<port>]
|
||||
"""
|
||||
import RPi.GPIO as GPIO
|
||||
import http.server
|
||||
import socketserver
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import cgi
|
||||
import time
|
||||
GPIO.setwarnings(False)
|
||||
os.system('pkill -9 httpd')
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
pinList = [2, 3, 4, 17]
|
||||
SleepTimeL=10
|
||||
|
||||
class S(http.server.SimpleHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
|
||||
GPIO.setup(pinList[0], GPIO.OUT)
|
||||
status0=int(GPIO.input(pinList[0]))
|
||||
GPIO.setup(pinList[1], GPIO.OUT)
|
||||
status1=int(GPIO.input(pinList[1]))
|
||||
GPIO.setup(pinList[2], GPIO.OUT)
|
||||
status2=int(GPIO.input(pinList[2]))
|
||||
GPIO.setup(pinList[3], GPIO.OUT)
|
||||
status3=int(GPIO.input(pinList[3]))
|
||||
#logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))
|
||||
|
||||
self._set_response()
|
||||
pass
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
|
||||
post_data = self.rfile.read(content_length) # <--- Gets the data itself
|
||||
logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
|
||||
str(self.path), str(self.headers), post_data.decode('utf-8'))
|
||||
|
||||
self._set_response()
|
||||
self.wfile.write("POST request for {}".format(self.path).encode('utf-8'))
|
||||
|
||||
def _set_response(self):
|
||||
#time.sleep(5)
|
||||
GPIO.setup(pinList[0], GPIO.OUT)
|
||||
status0=int(GPIO.input(pinList[0]))
|
||||
GPIO.setup(pinList[1], GPIO.OUT)
|
||||
status1=int(GPIO.input(pinList[1]))
|
||||
GPIO.setup(pinList[2], GPIO.OUT)
|
||||
status2=int(GPIO.input(pinList[2]))
|
||||
GPIO.setup(pinList[3], GPIO.OUT)
|
||||
status3=int(GPIO.input(pinList[3]))
|
||||
|
||||
objParsedPath = str(format(self.path).encode('utf-8'))
|
||||
print ('objParsedPath="'+objParsedPath+'"')
|
||||
|
||||
if objParsedPath == "b'/R0'":
|
||||
print ('R0 run')
|
||||
if status0 == 1:
|
||||
os.system('./relay.py open 0')
|
||||
else:
|
||||
os.system('./relay.py close 0')
|
||||
time.sleep(int(SleepTimeL));
|
||||
os.system('./relay.py open 0')
|
||||
|
||||
if objParsedPath == "b'/R0O'":
|
||||
print ('R0 run0')
|
||||
if status0 == 1:
|
||||
os.system('./relay.py open 0')
|
||||
else:
|
||||
os.system('./relay.py close 0')
|
||||
|
||||
|
||||
|
||||
if objParsedPath == "b'/R1'":
|
||||
print ('R1 run')
|
||||
if status1 == 1:
|
||||
os.system('./relay.py open 1')
|
||||
else:
|
||||
os.system('./relay.py close 1')
|
||||
time.sleep(int(SleepTimeL));
|
||||
os.system('./relay.py open 1')
|
||||
|
||||
if objParsedPath == "b'/R1O'":
|
||||
print ('R1 run0')
|
||||
if status1 == 1:
|
||||
os.system('./relay.py open 1')
|
||||
else:
|
||||
os.system('./relay.py close 1')
|
||||
|
||||
|
||||
if objParsedPath == "b'/R2'":
|
||||
print ('R2 run')
|
||||
if status2 == 1:
|
||||
os.system('./relay.py open 2')
|
||||
else:
|
||||
os.system('./relay.py close 2')
|
||||
time.sleep(int(SleepTimeL));
|
||||
os.system('./relay.py open 2')
|
||||
|
||||
if objParsedPath == "b'/R2O'":
|
||||
print ('R2 run0')
|
||||
if status2 == 1:
|
||||
os.system('./relay.py open 2')
|
||||
else:
|
||||
os.system('./relay.py close 2')
|
||||
|
||||
|
||||
if objParsedPath == "b'/R3'":
|
||||
print ('R3 run')
|
||||
if status3 == 1:
|
||||
os.system('./relay.py open 3')
|
||||
else:
|
||||
os.system('./relay.py close 3')
|
||||
time.sleep(int(SleepTimeL));
|
||||
os.system('./relay.py open 3')
|
||||
|
||||
if objParsedPath == "b'/R3O'":
|
||||
print ('R3 run0')
|
||||
if status3 == 1:
|
||||
os.system('./relay.py open 3')
|
||||
else:
|
||||
os.system('./relay.py close 3')
|
||||
|
||||
GPIO.setup(pinList[0], GPIO.OUT)
|
||||
status0=int(GPIO.input(pinList[0]))
|
||||
GPIO.setup(pinList[1], GPIO.OUT)
|
||||
status1=int(GPIO.input(pinList[1]))
|
||||
GPIO.setup(pinList[2], GPIO.OUT)
|
||||
status2=int(GPIO.input(pinList[2]))
|
||||
GPIO.setup(pinList[3], GPIO.OUT)
|
||||
status3=int(GPIO.input(pinList[3]))
|
||||
|
||||
|
||||
|
||||
file = read_html_template("index.html")
|
||||
self.send_response(200, "OK")
|
||||
self.end_headers()
|
||||
print ('Relay0 = '+str(status0))
|
||||
if status0 == 0:
|
||||
file = file.replace("CHECKED1", "checked")
|
||||
else:
|
||||
file = file.replace("CHECKED1", "")
|
||||
|
||||
print ('Relay1 = '+str(status1))
|
||||
if status1 == 0:
|
||||
file = file.replace("CHECKED2", "checked")
|
||||
else:
|
||||
file = file.replace("CHECKED2", "")
|
||||
|
||||
print ('Relay2 = '+str(status2))
|
||||
if status2 == 0:
|
||||
file = file.replace("CHECKED3", "checked")
|
||||
else:
|
||||
file = file.replace("CHECKED3", "")
|
||||
|
||||
print ('Relay3 = '+str(status3))
|
||||
if status3 == 0:
|
||||
file = file.replace("CHECKED4", "checked")
|
||||
else:
|
||||
file = file.replace("CHECKED4", "")
|
||||
|
||||
self.wfile.write(bytes(file.encode("utf-8")))
|
||||
|
||||
|
||||
|
||||
|
||||
def read_html_template(path):
|
||||
"""function to read HTML file"""
|
||||
try:
|
||||
with open(path) as f:
|
||||
file = f.read()
|
||||
except Exception as e:
|
||||
file = e
|
||||
return file
|
||||
|
||||
def run(handler_class=S, port=8000):
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
server_address = ('', port)
|
||||
#httpd = server_class(server_address, handler_class)
|
||||
logging.info('Starting httpd '+str(port)+' ...\n')
|
||||
try:
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
with socketserver.TCPServer(server_address, handler_class) as httpd:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
httpd.server_close()
|
||||
logging.info('Stopping httpd...\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
from sys import argv
|
||||
|
||||
if len(argv) == 2:
|
||||
run(port=int(argv[1]))
|
||||
else:
|
||||
run()
|
BIN
img/funduino_keyes_8_prises.jpg
Normal file
BIN
img/funduino_keyes_8_prises.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
img/relais.jpg
Normal file
BIN
img/relais.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
img/schema_funduino_keyes.jpg
Normal file
BIN
img/schema_funduino_keyes.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
180
index.html
Normal file
180
index.html
Normal file
|
@ -0,0 +1,180 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<style>
|
||||
|
||||
.onoffswitch {
|
||||
position: relative; width: 90px;
|
||||
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
||||
}
|
||||
.onoffswitch-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.onoffswitch-label {
|
||||
display: block; overflow: hidden; cursor: pointer;
|
||||
border: 2px solid #999999; border-radius: 20px;
|
||||
}
|
||||
.onoffswitch-inner {
|
||||
display: block; width: 200%; margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
||||
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
|
||||
font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.onoffswitch-inner:before {
|
||||
content: "ON";
|
||||
padding-left: 10px;
|
||||
background-color: #58f164; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "OFF";
|
||||
padding-right: 10px;
|
||||
background-color: #d3250d; color: #999999;
|
||||
text-align: right;
|
||||
}
|
||||
.onoffswitch-switch {
|
||||
display: block; width: 18px; margin: 6px;
|
||||
background: #FFFFFF;
|
||||
position: absolute; top: 0; bottom: 0;
|
||||
right: 56px;
|
||||
border: 2px solid #999999; border-radius: 20px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
table{
|
||||
caption-side: top;
|
||||
}
|
||||
th, td{
|
||||
boder: 1px solid black;
|
||||
padding: 10px;
|
||||
}
|
||||
caption{
|
||||
background-color: #0AD;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
<title>RelayCMD</title>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1">
|
||||
<caption>RELAY COMMAND</caption>
|
||||
<tr>
|
||||
<td><h3>Nom RELAY</h3></td>
|
||||
<td><h3>RESET Tempo10s</h3></td>
|
||||
<td><h3>ON/OFF</h3></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Relay 1 [HamNet]</td>
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch1" class="onoffswitch-checkbox" id="myonoffswitch" tabindex="0" CHECKED1 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R0','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch11" class="onoffswitch-checkbox" id="myonoffswitch11" tabindex="0" CHECKED1 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R0O','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch11">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Relay 2 [Srv/RadioSonde/ADSB]</td>
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch2" class="onoffswitch-checkbox" id="myonoffswitch2" tabindex="0" CHECKED2 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R1','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch2">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch22" class="onoffswitch-checkbox" id="myonoffswitch22" tabindex="0" CHECKED2 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R1O','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch22">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Relay 3 [HotSpot]</td>
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch3" class="onoffswitch-checkbox" id="myonoffswitch3" tabindex="0" CHECKED3 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R2','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch3">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch33" class="onoffswitch-checkbox" id="myonoffswitch33" tabindex="0" CHECKED3 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R2O','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch33">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Relay 4 [Alim]</td>
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch4" class="onoffswitch-checkbox" id="myonoffswitch4" tabindex="0" CHECKED4 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R3','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch4">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="onoffswitch44" class="onoffswitch-checkbox" id="myonoffswitch44" tabindex="0" CHECKED4 onclick="javascript:window.open('http://fra1od.freeboxos.fr:8000/R3O','_self');">
|
||||
<label class="onoffswitch-label" for="myonoffswitch44">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</body>
|
||||
</html>
|
135
relay.py
Executable file
135
relay.py
Executable file
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/python
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
GPIO.setwarnings(False)
|
||||
# loop through pins and set mode and state to 'high'
|
||||
def reset_relay(pinrelayList):
|
||||
for i in pinrelayList:
|
||||
GPIO.setup(i, GPIO.OUT)
|
||||
GPIO.output(i, GPIO.LOW)
|
||||
|
||||
#Power is 0 OFF or 1 ON
|
||||
def power(pinListpower,ONOFF):
|
||||
j=1
|
||||
for i in pinListpower:
|
||||
GPIO.setup(i, GPIO.OUT)
|
||||
if int(ONOFF) == 0:
|
||||
print ('Relay='+str(j)+' '+str(ONOFF))
|
||||
GPIO.output(i, GPIO.HIGH)
|
||||
if int(ONOFF) == 1:
|
||||
print ('Relay='+str(j)+' '+str(ONOFF))
|
||||
GPIO.output(i, GPIO.LOW)
|
||||
j+=1
|
||||
|
||||
#Status RELAY
|
||||
def status(pinListcheck,pincheck):
|
||||
j=1
|
||||
if pincheck==10:
|
||||
for i in pinListcheck:
|
||||
GPIO.setup(i, GPIO.OUT)
|
||||
|
||||
if int(GPIO.input(i)) == 1:
|
||||
print ('Relay '+str(j)+' = OFF')
|
||||
else:
|
||||
print ('Relay '+str(j)+' = ON')
|
||||
j+=1
|
||||
else:
|
||||
GPIO.setup(pinListcheck[int(pincheck)], GPIO.OUT)
|
||||
if int(GPIO.input(pinListcheck[int(pincheck)])) == 1:
|
||||
print ('Relay '+str(pincheck)+' = OFF')
|
||||
else:
|
||||
print ('Relay '+str(pincheck)+' = ON')
|
||||
|
||||
# init funct
|
||||
def main(argv=sys.argv):
|
||||
# init var
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
pinList = [2, 3, 4, 17]
|
||||
SleepTimeL = 5
|
||||
#GPIO.cleanup()
|
||||
x = 1
|
||||
#Cmd HELP
|
||||
if (len(argv) > x and argv[x] == 'help') or len(argv) == 1:
|
||||
print ('usage: ./relay.py ....')
|
||||
print ('options:')
|
||||
print (' help', 'This aide')
|
||||
print ('commands:')
|
||||
print (' close R', 'Close relay number R of 0 to 3')
|
||||
print (' open R', 'Open relay R of number 0 to 3')
|
||||
print (' reset R T', 'Reset device, changes ON OFF ON by tempo T.')
|
||||
print (' status', 'Print the states of all relays.')
|
||||
print ('example: ./relay.py reset 0 10')
|
||||
print (' relay0 is ON but make a reset by OFF STEP 10s and ON')
|
||||
print ('example: ./relay.py open 0')
|
||||
print ('example: ./relay.py close 1')
|
||||
print ('example: ./relay.py status')
|
||||
print ('(c) F4IYT Xavier 2022 v1.00')
|
||||
GPIO.cleanup()
|
||||
sys.exit(1)
|
||||
|
||||
#Cmd STATUS
|
||||
if len(argv) > x and argv[x] == 'status':
|
||||
pinstatus=10
|
||||
if len(argv) > 2:
|
||||
x +=1
|
||||
pinstatus=argv[x]
|
||||
|
||||
status(pinList,pinstatus)
|
||||
|
||||
#Cmd Close Relay
|
||||
if len(argv) > x and argv[x] == 'close':
|
||||
x +=1
|
||||
relay=argv[x]
|
||||
if int(relay) < 4:
|
||||
relayclose=pinList[int(relay)]
|
||||
print ("Close Relay:"+relay)
|
||||
GPIO.setup(int(relayclose), GPIO.OUT)
|
||||
GPIO.output(int(relayclose), GPIO.HIGH)
|
||||
else:
|
||||
print ('Error number of Relay check input only 0 at 3!')
|
||||
|
||||
#Cmd Open Relay
|
||||
if len(argv) > x and argv[x] == 'open':
|
||||
x +=1
|
||||
relay=argv[x]
|
||||
if int(relay) < 4:
|
||||
relayopen=pinList[int(relay)]
|
||||
print ("Open Relay:"+relay)
|
||||
GPIO.setup(int(relayopen), GPIO.OUT)
|
||||
GPIO.output(int(relayopen), GPIO.LOW)
|
||||
else:
|
||||
print ('Error number of Relay check input only 0 at 3!')
|
||||
|
||||
#Cmd Reset Relay
|
||||
if len(argv) > x and argv[x] == 'reset':
|
||||
x +=1
|
||||
relay=argv[x]
|
||||
if int(relay) < 4:
|
||||
relayreset=pinList[int(relay)]
|
||||
if len(argv) > 3:
|
||||
x +=1
|
||||
SleepTimeL=argv[x]
|
||||
print ("Reset Relay:"+relay+" tempo="+str(SleepTimeL))
|
||||
time.sleep(int(SleepTimeL));
|
||||
GPIO.setup(int(relayreset), GPIO.OUT)
|
||||
GPIO.output(int(relayreset), GPIO.HIGH)
|
||||
time.sleep(int(SleepTimeL));
|
||||
GPIO.setup(int(relayreset), GPIO.OUT)
|
||||
GPIO.output(int(relayreset), GPIO.LOW)
|
||||
else:
|
||||
print ('Error number of Relay check input only 0 at 3!')
|
||||
|
||||
|
||||
#Cmd Power Relay
|
||||
if len(argv) > x and argv[x] == 'power':
|
||||
x +=1
|
||||
ponoff=argv[x]
|
||||
print ("Power = :"+ponoff)
|
||||
power(pinList,ponoff)
|
||||
|
||||
|
||||
# Main part
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user