Start implementing complete protocol buffer protocol
This commit is contained in:
@@ -17,49 +17,117 @@ enum Module {
|
|||||||
|
|
||||||
message Command {
|
message Command {
|
||||||
enum Type {
|
enum Type {
|
||||||
SERVER = 1;
|
PING = 1;
|
||||||
PING = 2;
|
CONSTRUCT = 2;
|
||||||
CONSTRUCT = 3;
|
CLEAR_DISPLAY = 3;
|
||||||
SET_LED = 4;
|
CLEAR_DISPLAY_DIGIT = 4;
|
||||||
|
SET_DISPLAY = 5;
|
||||||
|
SET_DISPLAY_DIGIT = 6;
|
||||||
|
SET_DISPLAY_TO_BIN_NUMBER = 7;
|
||||||
|
SET_DISPLAY_TO_DEC_NUMBER = 8;
|
||||||
|
SET_DISPLAY_TO_HEX_NUMBER = 9;
|
||||||
|
SET_DISPLAY_TO_ERROR = 10;
|
||||||
|
SET_DISPLAY_TO_STRING = 11;
|
||||||
|
SET_LED = 12;
|
||||||
|
SET_LEDS = 13;
|
||||||
|
SETUP_DISPLAY = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
required Type type = 1;
|
required Type type = 1;
|
||||||
optional Server server = 2;
|
optional Ping ping = 2;
|
||||||
optional Ping ping = 3;
|
optional Construct construct = 3;
|
||||||
optional Construct construct = 4;
|
optional ClearDisplayDigit clearDisplayDigit = 4;
|
||||||
optional SetLed setLed = 5;
|
optional SetDisplay setDisplay = 5;
|
||||||
}
|
optional SetDisplayDigit setDisplayDigit = 6;
|
||||||
|
optional SetDisplayToNumber setDisplayToNumber = 7;
|
||||||
message Server {
|
optional SetDisplayToString setDisplayToString = 8;
|
||||||
optional string host = 1;
|
optional SetLED setLED = 9;
|
||||||
required int32 port = 2;
|
optional SetLEDs setLEDs = 10;
|
||||||
|
optional SetupDisplay setupDisplay = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Ping {
|
message Ping {
|
||||||
required int32 id = 1;
|
required int32 id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Echo {
|
|
||||||
required int32 id = 1;
|
|
||||||
optional string message = 2 [(nanopb).max_size = 40];
|
|
||||||
}
|
|
||||||
|
|
||||||
message Construct {
|
message Construct {
|
||||||
required int32 dataPin = 1;
|
required int32 dataPin = 1;
|
||||||
required int32 clockPin = 2;
|
required int32 clockPin = 2;
|
||||||
optional int32 strobePin = 3;
|
optional int32 strobePin = 3;
|
||||||
optional bool activateDisplay = 4 [default = true];
|
|
||||||
optional int32 intensity = 5 [default = 7];
|
|
||||||
optional Module module = 6 [default = TM1638];
|
optional Module module = 6 [default = TM1638];
|
||||||
optional int32 id = 7 [default = 0];
|
optional int32 id = 7 [default = 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetLed {
|
message ClearDisplayDigit {
|
||||||
|
required int32 pos = 1;
|
||||||
|
required bool dot = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetDisplay {
|
||||||
|
required bytes values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetDisplayDigit {
|
||||||
|
required int32 digit = 1;
|
||||||
|
required int32 pos = 2;
|
||||||
|
required bool dot = 3;
|
||||||
|
required bool has_font = 4;
|
||||||
|
optional bytes font = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetDisplayToNumber {
|
||||||
|
required sint32 number = 1;
|
||||||
|
required int32 dots = 2;
|
||||||
|
required bool has_font = 3;
|
||||||
|
optional bytes font = 4;
|
||||||
|
optional bool leadingZeros = 5 [default = false];
|
||||||
|
optional bool sign = 6 [default = true];
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetDisplayToString {
|
||||||
|
required string string = 1 [(nanopb).max_size = 40];
|
||||||
|
required int32 dots = 2;
|
||||||
|
required int32 pos = 3;
|
||||||
|
required bool has_font = 4;
|
||||||
|
optional bytes font = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetLED {
|
||||||
required Color color = 1;
|
required Color color = 1;
|
||||||
required int32 pos = 2;
|
required int32 pos = 2;
|
||||||
optional int32 id = 3 [default = 1];
|
optional int32 id = 3 [default = 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SetLEDs {
|
||||||
|
required int32 led = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetupDisplay {
|
||||||
|
optional bool active = 1 [default = true];
|
||||||
|
optional int32 intensity = 2 [default = 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
enum Type {
|
||||||
|
PONG = 1;
|
||||||
|
BUTTONS = 2;
|
||||||
|
TEXT = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
required Type type = 1;
|
||||||
|
optional Pong pong = 2;
|
||||||
|
optional Buttons buttons = 3;
|
||||||
|
optional Text text = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Pong {
|
||||||
|
required int32 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Buttons {
|
message Buttons {
|
||||||
required byte buttons;
|
required int32 buttons = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Text {
|
||||||
|
required string text = 1 [(nanopb).max_size = 40];
|
||||||
}
|
}
|
||||||
297
src/main.cpp
297
src/main.cpp
@@ -15,43 +15,113 @@ TM1640 *tm1640;
|
|||||||
TM1638 *tm1638[6];
|
TM1638 *tm1638[6];
|
||||||
TM16XX *module;
|
TM16XX *module;
|
||||||
|
|
||||||
int t = 123;
|
tm1638_Construct construct;
|
||||||
int id = 0;
|
int pong = 0;
|
||||||
|
String text;
|
||||||
byte pressed = 0;
|
byte pressed = 0;
|
||||||
_tm1638_Construct construct;
|
|
||||||
|
|
||||||
void setup() {
|
byte values[256];
|
||||||
Serial.begin(9600);
|
uint8_t font[256];
|
||||||
tm1638[0] = new TM1638(8, 9, 7);
|
|
||||||
tm1638[0]->setupDisplay(true, 1);
|
|
||||||
tm1638[0]->setLED(TM1638_COLOR_RED, 2);
|
|
||||||
tm1638[0]->setDisplayToDecNumber(111, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendButtons(int id, byte buttons) {
|
void sendMessage(_tm1638_Message message) {
|
||||||
/*_tm1638_Buttons x = {id, buttons};
|
|
||||||
uint8_t out_buffer[256];
|
uint8_t out_buffer[256];
|
||||||
pb_ostream_t ostream = pb_ostream_from_buffer(out_buffer, sizeof(out_buffer));
|
pb_ostream_t ostream = pb_ostream_from_buffer(out_buffer, sizeof(out_buffer));
|
||||||
if (pb_encode_delimited(&ostream, tm1638_Buttons_fields, &x)) {
|
if (pb_encode_delimited(&ostream, tm1638_Message_fields, &message)) {
|
||||||
Serial.write(out_buffer, ostream.bytes_written);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendEcho(int id, char message[]) {
|
|
||||||
_tm1638_Echo echo = {id, true};
|
|
||||||
strncpy(echo.message, message, 40);
|
|
||||||
uint8_t out_buffer[256];
|
|
||||||
pb_ostream_t ostream = pb_ostream_from_buffer(out_buffer, sizeof(out_buffer));
|
|
||||||
if (pb_encode_delimited(&ostream, tm1638_Echo_fields, &echo)) {
|
|
||||||
Serial.write(out_buffer, ostream.bytes_written);
|
Serial.write(out_buffer, ostream.bytes_written);
|
||||||
|
Serial.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendPong(int id) {
|
||||||
|
tm1638_Pong pong = {id};
|
||||||
|
tm1638_Message message = tm1638_Message_init_default;
|
||||||
|
message.type = tm1638_Message_Type_PONG;
|
||||||
|
message.pong = pong;
|
||||||
|
message.has_pong = true;
|
||||||
|
sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendText(char buffer[]) {
|
||||||
|
tm1638_Text text = tm1638_Text_init_default;
|
||||||
|
tm1638_Message message = tm1638_Message_init_default;
|
||||||
|
message.type = tm1638_Message_Type_TEXT;
|
||||||
|
strcpy(message.text.text, buffer);
|
||||||
|
message.has_text = true;
|
||||||
|
sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendText(String string) {
|
||||||
|
char buffer[40];
|
||||||
|
string.toCharArray(buffer, sizeof(buffer));
|
||||||
|
sendText(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendButtons(byte pressed) {
|
||||||
|
tm1638_Buttons buttons = {pressed};
|
||||||
|
tm1638_Message message = tm1638_Message_init_default;
|
||||||
|
message.type = tm1638_Message_Type_BUTTONS;
|
||||||
|
message.buttons = buttons;
|
||||||
|
message.has_buttons = true;
|
||||||
|
sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte parseColor(tm1638_Color color) {
|
||||||
|
switch (color) {
|
||||||
|
case tm1638_Color_GREEN:
|
||||||
|
return TM1638_COLOR_GREEN;
|
||||||
|
case tm1638_Color_RED:
|
||||||
|
return TM1638_COLOR_RED;
|
||||||
|
case tm1638_Color_BOTH:
|
||||||
|
return TM1638_COLOR_GREEN + TM1638_COLOR_RED;
|
||||||
|
case tm1638_Color_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return TM1638_COLOR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool callbackValues(pb_istream_t *stream, const pb_field_t *field, void **arg) {
|
||||||
|
memset(values, 0, sizeof(values));
|
||||||
|
if (stream->bytes_left > sizeof(values) - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!pb_read(stream, values, stream->bytes_left)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool callbackFont(pb_istream_t *stream, const pb_field_t *field, void **arg) {
|
||||||
|
memset(font, 0, sizeof(font));
|
||||||
|
if (stream->bytes_left > sizeof(font) - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!pb_read(stream, font, stream->bytes_left)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
tm1638[0] = new TM1638(8, 9, 7);
|
||||||
|
tm1638[0]->setupDisplay(true, 1);
|
||||||
|
tm1638[0]->setDisplayToError();
|
||||||
|
|
||||||
|
Serial.begin(19200);
|
||||||
|
sendText("setup()");
|
||||||
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
if (pong > 0) {
|
||||||
|
sendPong(pong);
|
||||||
|
pong = 0;
|
||||||
|
}
|
||||||
|
if (text.length() > 0) {
|
||||||
|
sendText(text);
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
byte buttons = tm1638[0]->getButtons();
|
byte buttons = tm1638[0]->getButtons();
|
||||||
if (buttons != pressed) {
|
if (buttons != pressed) {
|
||||||
//sendButtons(0, buttons);
|
sendButtons(buttons);
|
||||||
sendEcho(buttons, "Buttons!");
|
|
||||||
pressed = buttons;
|
pressed = buttons;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,51 +140,158 @@ void serialEvent() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tm1638[0]->setDisplayDigit(command.type / 10, 0, false);
|
||||||
|
// tm1638[0]->setDisplayDigit(command.type, 1, false);
|
||||||
|
|
||||||
switch (command.type) {
|
switch (command.type) {
|
||||||
case tm1638_Command_Type_PING:
|
case tm1638_Command_Type_PING:
|
||||||
if (command.has_ping) {
|
if (command.has_ping) {
|
||||||
_tm1638_Ping ping = command.ping;
|
tm1638_Ping ping = command.ping;
|
||||||
sendEcho(ping.id, "Pong");
|
tm1638[0]->setDisplayToDecNumber(ping.id, 0, false);
|
||||||
tm1638[0]->setDisplayToDecNumber(ping.id, 0);
|
pong = ping.id;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case tm1638_Command_Type_CONSTRUCT:
|
case tm1638_Command_Type_CONSTRUCT:
|
||||||
construct = command.construct;
|
if (command.has_construct) {
|
||||||
id = construct.id;
|
construct = command.construct;
|
||||||
switch (construct.module) {
|
switch (construct.module) {
|
||||||
case tm1638_Module_TM1638:
|
case tm1638_Module_TM1638:
|
||||||
tm1638[0] = new TM1638((byte) construct.dataPin, (byte) construct.clockPin, (byte) construct.strobePin, construct.activateDisplay, (byte) construct.intensity);
|
tm1638[0] = new TM1638((byte) construct.dataPin, (byte) construct.clockPin, (byte) construct.strobePin);
|
||||||
tm1638[0]->setupDisplay(true, 1);
|
break;
|
||||||
break;
|
case tm1638_Module_InvertedTM1638:
|
||||||
case tm1638_Module_InvertedTM1638:
|
tm1638[0] = new InvertedTM1638((byte) construct.dataPin, (byte) construct.clockPin, (byte) construct.strobePin);
|
||||||
tm1638[0] = new InvertedTM1638((byte) construct.dataPin, (byte) construct.clockPin, (byte) construct.strobePin, construct.activateDisplay, (byte) construct.intensity);
|
break;
|
||||||
break;
|
case tm1638_Module_TM1640:
|
||||||
case tm1638_Module_TM1640:
|
tm1640 = new TM1640((byte) construct.dataPin, (byte) construct.clockPin);
|
||||||
tm1640 = new TM1640((byte) construct.dataPin, (byte) construct.clockPin, construct.activateDisplay, (byte) construct.intensity);
|
break;
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_CLEAR_DISPLAY:
|
||||||
|
tm1638[0]->clearDisplay();
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_CLEAR_DISPLAY_DIGIT:
|
||||||
|
if (command.has_clearDisplayDigit) {
|
||||||
|
tm1638_ClearDisplayDigit clearDisplayDigit = command.clearDisplayDigit;
|
||||||
|
tm1638[0]->clearDisplayDigit(clearDisplayDigit.pos, clearDisplayDigit.dot);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY:
|
||||||
|
if (command.has_setDisplay) {
|
||||||
|
tm1638_SetDisplay setDisplay = command.setDisplay;
|
||||||
|
setDisplay.values.funcs.decode = &callbackValues;
|
||||||
|
if (pb_decode(&istream, tm1638_SetDisplay_fields, &setDisplay)) {
|
||||||
|
tm1638[0]->setDisplay(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_DIGIT:
|
||||||
|
if (command.has_setDisplayDigit) {
|
||||||
|
tm1638_SetDisplayDigit setDisplayDigit = command.setDisplayDigit;
|
||||||
|
byte digit = setDisplayDigit.digit;
|
||||||
|
byte pos = setDisplayDigit.pos;
|
||||||
|
bool dot = setDisplayDigit.dot;
|
||||||
|
bool has_font = setDisplayDigit.has_font;
|
||||||
|
if (has_font) {
|
||||||
|
setDisplayDigit.font.funcs.decode = &callbackFont;
|
||||||
|
if (pb_decode(&istream, tm1638_SetDisplayDigit_fields, &setDisplayDigit)) {
|
||||||
|
tm1638[0]->setDisplayDigit(digit, pos, dot, font);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tm1638[0]->setDisplayDigit(digit, pos, dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_TO_BIN_NUMBER:
|
||||||
|
if (command.has_setDisplayToNumber) {
|
||||||
|
tm1638_SetDisplayToNumber setDisplayToNumber = command.setDisplayToNumber;
|
||||||
|
byte number = setDisplayToNumber.number;
|
||||||
|
byte dots = setDisplayToNumber.dots;
|
||||||
|
bool has_font = setDisplayToNumber.has_font;
|
||||||
|
if (has_font) {
|
||||||
|
setDisplayToNumber.font.funcs.decode = &callbackFont;
|
||||||
|
if (pb_decode(&istream, tm1638_SetDisplayToNumber_fields, &setDisplayToNumber)) {
|
||||||
|
tm1638[0]->setDisplayToBinNumber(number, dots, font);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tm1638[0]->setDisplayToBinNumber(number, dots);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_TO_DEC_NUMBER:
|
||||||
|
if (command.has_setDisplayToNumber) {
|
||||||
|
tm1638_SetDisplayToNumber setDisplayToNumber = command.setDisplayToNumber;
|
||||||
|
signed int number = setDisplayToNumber.number;
|
||||||
|
byte dots = setDisplayToNumber.dots;
|
||||||
|
bool leadingZeros = setDisplayToNumber.leadingZeros;
|
||||||
|
bool has_font = setDisplayToNumber.has_font;
|
||||||
|
if (has_font) {
|
||||||
|
setDisplayToNumber.font.funcs.decode = &callbackFont;
|
||||||
|
if (pb_decode(&istream, tm1638_SetDisplayToNumber_fields, &setDisplayToNumber)) {
|
||||||
|
if (number < 0) {
|
||||||
|
tm1638[0]->setDisplayToSignedDecNumber(number, dots, leadingZeros, font);
|
||||||
|
} else {
|
||||||
|
tm1638[0]->setDisplayToDecNumber(number, dots, leadingZeros, font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (number < 0) {
|
||||||
|
tm1638[0]->setDisplayToSignedDecNumber(number, dots, leadingZeros);
|
||||||
|
} else {
|
||||||
|
tm1638[0]->setDisplayToDecNumber(number, dots, leadingZeros);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_TO_HEX_NUMBER:
|
||||||
|
if (command.has_setDisplayToNumber) {
|
||||||
|
tm1638_SetDisplayToNumber setDisplayToNumber = command.setDisplayToNumber;
|
||||||
|
signed int number = setDisplayToNumber.number;
|
||||||
|
byte dots = setDisplayToNumber.dots;
|
||||||
|
bool leadingZeros = setDisplayToNumber.leadingZeros;
|
||||||
|
tm1638[0]->setDisplayToHexNumber(number, dots, leadingZeros);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_TO_ERROR:
|
||||||
|
tm1638[0]->setDisplayToError();
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SET_DISPLAY_TO_STRING:
|
||||||
|
if(command.has_setDisplayToString) {
|
||||||
|
tm1638_SetDisplayToString setDisplayToString = command.setDisplayToString;
|
||||||
|
pb_decode(&istream, tm1638_SetDisplayToString_fields, &setDisplayToString);
|
||||||
|
byte dots = setDisplayToString.dots;
|
||||||
|
byte pos = setDisplayToString.dots;
|
||||||
|
tm1638[0]->setDisplayToString(setDisplayToString.string, dots, pos);
|
||||||
|
bool has_font = setDisplayToString.has_font;
|
||||||
|
if (has_font) {
|
||||||
|
setDisplayToString.font.funcs.decode = &callbackFont;
|
||||||
|
if (pb_decode(&istream, tm1638_SetDisplayToString_fields, &setDisplayToString)) {
|
||||||
|
tm1638[0]->setDisplayToString(setDisplayToString.string, dots, pos, font);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tm1638[0]->setDisplayToString(setDisplayToString.string, dots, pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case tm1638_Command_Type_SET_LED:
|
case tm1638_Command_Type_SET_LED:
|
||||||
if (command.has_setLed) {
|
if (command.has_setLED) {
|
||||||
_tm1638_SetLed setLed = command.setLed;
|
tm1638_SetLED setLED = command.setLED;
|
||||||
byte color;
|
byte color = parseColor(setLED.color);
|
||||||
switch (setLed.color) {
|
tm1638[0]->setLED(color, setLED.pos);
|
||||||
case tm1638_Color_GREEN:
|
|
||||||
color = TM1638_COLOR_GREEN;
|
|
||||||
break;
|
|
||||||
case tm1638_Color_RED:
|
|
||||||
color = TM1638_COLOR_RED;
|
|
||||||
break;
|
|
||||||
case tm1638_Color_BOTH:
|
|
||||||
color = TM1638_COLOR_GREEN + TM1638_COLOR_RED;
|
|
||||||
break;
|
|
||||||
case tm1638_Color_NONE:
|
|
||||||
color = TM1638_COLOR_NONE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tm1638[0]->setLED(color, setLed.pos);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case tm1638_Command_Type_SET_LEDS:
|
||||||
|
if (command.has_setLEDs) {
|
||||||
|
tm1638_SetLEDs setLEDs = command.setLEDs;
|
||||||
|
tm1638[0]->setLEDs(setLEDs.led);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case tm1638_Command_Type_SETUP_DISPLAY:
|
||||||
|
if (command.has_setupDisplay) {
|
||||||
|
tm1638_SetupDisplay setupDisplay = command.setupDisplay;
|
||||||
|
tm1638[0]->setupDisplay(setupDisplay.active, setupDisplay.intensity);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user