NMEA2000 manufacturer names and a bit more logging
This commit is contained in:
@@ -55,7 +55,7 @@ bool Nmea2kTwai::CANOpen()
|
|||||||
}
|
}
|
||||||
esp_err_t rt = twai_start();
|
esp_err_t rt = twai_start();
|
||||||
if (rt != ESP_OK) {
|
if (rt != ESP_OK) {
|
||||||
LOGE(TAG, "CANOpen failed: %x", (int)rt);
|
LOGE(TAG, "CANOpen failed: %x", rt);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
LOGI(TAG, "CANOpen ok");
|
LOGI(TAG, "CANOpen ok");
|
||||||
@@ -101,9 +101,9 @@ void Nmea2kTwai::initDriver()
|
|||||||
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
||||||
esp_err_t rt = twai_driver_install(&g_config, &t_config, &f_config);
|
esp_err_t rt = twai_driver_install(&g_config, &t_config, &f_config);
|
||||||
if (rt == ESP_OK) {
|
if (rt == ESP_OK) {
|
||||||
// logDebug(LOG_INFO,"twai driver initialzed, rx=%d,tx=%d",(int)RxPin,(int)TxPin);
|
LOGI(TAG, "twai driver initialzed, rx=%d,tx=%d", RxPin, TxPin);
|
||||||
} else {
|
} else {
|
||||||
// logDebug(LOG_ERR,"twai driver init failed: %x",(int)rt);
|
LOGE(TAG,"twai driver init failed: %x", rt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ void Nmea2kTwai::initDriver()
|
|||||||
void Nmea2kTwai::InitCANFrameBuffers()
|
void Nmea2kTwai::InitCANFrameBuffers()
|
||||||
{
|
{
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
// logDebug(LOG_INFO,"twai init - disabled");
|
LOGI(TAG, "twai init - disabled");
|
||||||
} else{
|
} else{
|
||||||
initDriver();
|
initDriver();
|
||||||
}
|
}
|
||||||
@@ -170,11 +170,11 @@ bool Nmea2kTwai::checkRecovery()
|
|||||||
if (canState.state == Nmea2kTwai::ST_BUS_OFF) {
|
if (canState.state == Nmea2kTwai::ST_BUS_OFF) {
|
||||||
strt = true;
|
strt = true;
|
||||||
bool rt = startRecovery();
|
bool rt = startRecovery();
|
||||||
// logDebug(LOG_INFO, "twai BUS_OFF: start can recovery - result %d", (int)rt);
|
LOGI(TAG, "twai BUS_OFF: start can recovery - result %d", rt);
|
||||||
}
|
}
|
||||||
if (canState.state == Nmea2kTwai::ST_STOPPED) {
|
if (canState.state == Nmea2kTwai::ST_STOPPED) {
|
||||||
bool rt = CANOpen();
|
bool rt = CANOpen();
|
||||||
// logDebug(LOG_INFO, "twai STOPPED: restart can driver - result %d", (int)rt);
|
LOGI(TAG, "twai STOPPED: restart can driver - result %d", rt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strt;
|
return strt;
|
||||||
@@ -185,20 +185,22 @@ void Nmea2kTwai::loop()
|
|||||||
if (disabled) {
|
if (disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
LOGW(TAG, "twai loop() not implemented");
|
||||||
|
// TODO?
|
||||||
// timers.loop();
|
// timers.loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Nmea2kTwai::Status Nmea2kTwai::logStatus()
|
Nmea2kTwai::Status Nmea2kTwai::logStatus()
|
||||||
{
|
{
|
||||||
Status canState = getStatus();
|
Status canState = getStatus();
|
||||||
/* logDebug(LOG_INFO, "twai state %s, rxerr %d, txerr %d, txfail %d, txtimeout %d, rxmiss %d, rxoverrun %d",
|
LOGI(TAG, "twai state %s, rxerr %d, txerr %d, txfail %d, txtimeout %d, rxmiss %d, rxoverrun %d",
|
||||||
stateStr(canState.state),
|
stateStr(canState.state),
|
||||||
canState.rx_errors,
|
canState.rx_errors,
|
||||||
canState.tx_errors,
|
canState.tx_errors,
|
||||||
canState.tx_failed,
|
canState.tx_failed,
|
||||||
canState.tx_timeouts,
|
canState.tx_timeouts,
|
||||||
canState.rx_missed,
|
canState.rx_missed,
|
||||||
canState.rx_overrun); */
|
canState.rx_overrun);
|
||||||
return canState;
|
return canState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +212,7 @@ bool Nmea2kTwai::startRecovery()
|
|||||||
lastRecoveryStart = millis();
|
lastRecoveryStart = millis();
|
||||||
esp_err_t rt = twai_driver_uninstall();
|
esp_err_t rt = twai_driver_uninstall();
|
||||||
if (rt != ESP_OK) {
|
if (rt != ESP_OK) {
|
||||||
// logDebug(LOG_ERR,"twai: deinit for recovery failed with %x",(int)rt);
|
LOGE(TAG, "twai: deinit for recovery failed with %x", rt);
|
||||||
}
|
}
|
||||||
initDriver();
|
initDriver();
|
||||||
bool frt = CANOpen();
|
bool frt = CANOpen();
|
||||||
@@ -229,3 +231,204 @@ const char * Nmea2kTwai::stateStr(const Nmea2kTwai::STATE &st)
|
|||||||
}
|
}
|
||||||
return "ERROR";
|
return "ERROR";
|
||||||
}
|
}
|
||||||
|
const Nmea2kTwai::ManufacturerEntry Nmea2kTwai::ManufacturerTable[] = {
|
||||||
|
{0, "Reserved"},
|
||||||
|
{69, "ARKS Enterprises, Inc."},
|
||||||
|
{78, "FW Murphy/Enovation Controls"},
|
||||||
|
{80, "Twin Disc"},
|
||||||
|
{85, "Kohler Power Systems"},
|
||||||
|
{88, "Hemisphere GPS Inc"},
|
||||||
|
{116, "BEP Marine"},
|
||||||
|
{135, "Airmar"},
|
||||||
|
{137, "Maretron"},
|
||||||
|
{140, "Lowrance"},
|
||||||
|
{144, "Mercury Marine"},
|
||||||
|
{147, "Nautibus Electronic GmbH"},
|
||||||
|
{148, "Blue Water Data"},
|
||||||
|
{154, "Westerbeke"},
|
||||||
|
{161, "Offshore Systems (UK) Ltd."},
|
||||||
|
{163, "Evinrude/BRP"},
|
||||||
|
{165, "CPAC Systems AB"},
|
||||||
|
{168, "Xantrex Technology Inc."},
|
||||||
|
{172, "Yanmar Marine"},
|
||||||
|
{174, "Volvo Penta"},
|
||||||
|
{175, "Honda Marine"},
|
||||||
|
{176, "Carling Technologies Inc. (Moritz Aerospace)"},
|
||||||
|
{185, "Beede Instruments"},
|
||||||
|
{192, "Floscan Instrument Co. Inc."},
|
||||||
|
{193, "Nobletec"},
|
||||||
|
{198, "Mystic Valley Communications"},
|
||||||
|
{199, "Actia"},
|
||||||
|
{200, "Honda Marine"},
|
||||||
|
{201, "Disenos Y Technologia"},
|
||||||
|
{211, "Digital Switching Systems"},
|
||||||
|
{215, "Xintex/Atena"},
|
||||||
|
{224, "EMMI NETWORK S.L."},
|
||||||
|
{225, "Honda Marine"},
|
||||||
|
{228, "ZF"},
|
||||||
|
{229, "Garmin"},
|
||||||
|
{233, "Yacht Monitoring Solutions"},
|
||||||
|
{235, "Sailormade Marine Telemetry/Tetra Technology LTD"},
|
||||||
|
{243, "Eride"},
|
||||||
|
{250, "Honda Marine"},
|
||||||
|
{257, "Honda Motor Company LTD"},
|
||||||
|
{272, "Groco"},
|
||||||
|
{273, "Actisense"},
|
||||||
|
{274, "Amphenol LTW Technology"},
|
||||||
|
{275, "Navico"},
|
||||||
|
{283, "Hamilton Jet"},
|
||||||
|
{285, "Sea Recovery"},
|
||||||
|
{286, "Coelmo SRL Italy"},
|
||||||
|
{295, "BEP Marine"},
|
||||||
|
{304, "Empir Bus"},
|
||||||
|
{305, "NovAtel"},
|
||||||
|
{306, "Sleipner Motor AS"},
|
||||||
|
{307, "MBW Technologies"},
|
||||||
|
{311, "Fischer Panda"},
|
||||||
|
{315, "ICOM"},
|
||||||
|
{328, "Qwerty"},
|
||||||
|
{329, "Dief"},
|
||||||
|
{341, "Böning Automationstechnologie GmbH & Co. KG"},
|
||||||
|
{345, "Korean Maritime University"},
|
||||||
|
{351, "Thrane and Thrane"},
|
||||||
|
{355, "Mastervolt"},
|
||||||
|
{356, "Fischer Panda Generators"},
|
||||||
|
{358, "Victron Energy"},
|
||||||
|
{370, "Rolls Royce Marine"},
|
||||||
|
{373, "Electronic Design"},
|
||||||
|
{374, "Northern Lights"},
|
||||||
|
{378, "Glendinning"},
|
||||||
|
{381, "B & G"},
|
||||||
|
{384, "Rose Point Navigation Systems"},
|
||||||
|
{385, "Johnson Outdoors Marine Electronics Inc Geonav"},
|
||||||
|
{394, "Capi 2"},
|
||||||
|
{396, "Beyond Measure"},
|
||||||
|
{400, "Livorsi Marine"},
|
||||||
|
{404, "ComNav"},
|
||||||
|
{409, "Chetco"},
|
||||||
|
{419, "Fusion Electronics"},
|
||||||
|
{421, "Standard Horizon"},
|
||||||
|
{422, "True Heading AB"},
|
||||||
|
{426, "Egersund Marine Electronics AS"},
|
||||||
|
{427, "em-trak Marine Electronics"},
|
||||||
|
{431, "Tohatsu Co, JP"},
|
||||||
|
{437, "Digital Yacht"},
|
||||||
|
{438, "Comar Systems Limited"},
|
||||||
|
{440, "Cummins"},
|
||||||
|
{443, "VDO (aka Continental-Corporation)"},
|
||||||
|
{451, "Parker Hannifin aka Village Marine Tech"},
|
||||||
|
{459, "Alltek Marine Electronics Corp"},
|
||||||
|
{460, "SAN GIORGIO S.E.I.N"},
|
||||||
|
{466, "Veethree Electronics & Marine"},
|
||||||
|
{467, "Humminbird Marine Electronics"},
|
||||||
|
{470, "SI-TEX Marine Electronics"},
|
||||||
|
{471, "Sea Cross Marine AB"},
|
||||||
|
{475, "GME aka Standard Communications Pty LTD"},
|
||||||
|
{476, "Humminbird Marine Electronics"},
|
||||||
|
{478, "Ocean Sat BV"},
|
||||||
|
{481, "Chetco Digitial Instruments"},
|
||||||
|
{493, "Watcheye"},
|
||||||
|
{499, "Lcj Capteurs"},
|
||||||
|
{502, "Attwood Marine"},
|
||||||
|
{503, "Naviop S.R.L."},
|
||||||
|
{504, "Vesper Marine Ltd"},
|
||||||
|
{510, "Marinesoft Co. LTD"},
|
||||||
|
{517, "NoLand Engineering"},
|
||||||
|
{518, "Transas USA"},
|
||||||
|
{529, "National Instruments Korea"},
|
||||||
|
{532, "Onwa Marine"},
|
||||||
|
{571, "Marinecraft (South Korea)"},
|
||||||
|
{573, "McMurdo Group aka Orolia LTD"},
|
||||||
|
{578, "Advansea"},
|
||||||
|
{579, "KVH"},
|
||||||
|
{580, "San Jose Technology"},
|
||||||
|
{583, "Yacht Control"},
|
||||||
|
{586, "Suzuki Motor Corporation"},
|
||||||
|
{591, "US Coast Guard"},
|
||||||
|
{595, "Ship Module aka Customware"},
|
||||||
|
{600, "Aquatic AV"},
|
||||||
|
{605, "Aventics GmbH"},
|
||||||
|
{606, "Intellian"},
|
||||||
|
{612, "SamwonIT"},
|
||||||
|
{614, "Arlt Tecnologies"},
|
||||||
|
{637, "Bavaria Yacts"},
|
||||||
|
{641, "Diverse Yacht Services"},
|
||||||
|
{644, "Wema U.S.A dba KUS"},
|
||||||
|
{645, "Garmin"},
|
||||||
|
{658, "Shenzhen Jiuzhou Himunication"},
|
||||||
|
{688, "Rockford Corp"},
|
||||||
|
{704, "JL Audio"},
|
||||||
|
{715, "Autonnic"},
|
||||||
|
{717, "Yacht Devices"},
|
||||||
|
{734, "REAP Systems"},
|
||||||
|
{735, "Au Electronics Group"},
|
||||||
|
{739, "LxNav"},
|
||||||
|
{743, "DaeMyung"},
|
||||||
|
{744, "Woosung"},
|
||||||
|
{773, "Clarion US"},
|
||||||
|
{776, "HMI Systems"},
|
||||||
|
{777, "Ocean Signal"},
|
||||||
|
{778, "Seekeeper"},
|
||||||
|
{781, "Poly Planar"},
|
||||||
|
{785, "Fischer Panda DE"},
|
||||||
|
{795, "Broyda Industries"},
|
||||||
|
{796, "Canadian Automotive"},
|
||||||
|
{797, "Tides Marine"},
|
||||||
|
{798, "Lumishore"},
|
||||||
|
{799, "Still Water Designs and Audio"},
|
||||||
|
{802, "BJ Technologies (Beneteau)"},
|
||||||
|
{803, "Gill Sensors"},
|
||||||
|
{811, "Blue Water Desalination"},
|
||||||
|
{815, "FLIR"},
|
||||||
|
{824, "Undheim Systems"},
|
||||||
|
{838, "TeamSurv"},
|
||||||
|
{844, "Fell Marine"},
|
||||||
|
{847, "Oceanvolt"},
|
||||||
|
{862, "Prospec"},
|
||||||
|
{868, "Data Panel Corp"},
|
||||||
|
{890, "L3 Technologies"},
|
||||||
|
{894, "Rhodan Marine Systems"},
|
||||||
|
{896, "Nexfour Solutions"},
|
||||||
|
{905, "ASA Electronics"},
|
||||||
|
{909, "Marines Co (South Korea)"},
|
||||||
|
{911, "Nautic-on"},
|
||||||
|
{930, "Ecotronix"},
|
||||||
|
{962, "Timbolier Industries"},
|
||||||
|
{963, "TJC Micro"},
|
||||||
|
{968, "Cox Powertrain"},
|
||||||
|
{969, "Blue Seas"},
|
||||||
|
{1850, "Teleflex Marine (SeaStar Solutions)"},
|
||||||
|
{1851, "Raymarine"},
|
||||||
|
{1852, "Navionics"},
|
||||||
|
{1853, "Japan Radio Co"},
|
||||||
|
{1854, "Northstar Technologies"},
|
||||||
|
{1855, "Furuno"},
|
||||||
|
{1856, "Trimble"},
|
||||||
|
{1857, "Simrad"},
|
||||||
|
{1858, "Litton"},
|
||||||
|
{1859, "Kvasar AB"},
|
||||||
|
{1860, "MMP"},
|
||||||
|
{1861, "Vector Cantech"},
|
||||||
|
{1862, "Yamaha Marine"},
|
||||||
|
{1863, "Faria Instruments"},
|
||||||
|
{2046, "Open Boat Projects"}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* Nmea2kTwai::GetManufacturerName(uint16_t code) {
|
||||||
|
constexpr size_t ManufacturerCount =
|
||||||
|
sizeof(Nmea2kTwai::ManufacturerTable) /
|
||||||
|
sizeof(Nmea2kTwai::ManufacturerTable[0]);
|
||||||
|
int low = 0;
|
||||||
|
int high = ManufacturerCount - 1;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
int mid = (low + high) / 2;
|
||||||
|
uint16_t midCode = ManufacturerTable[mid].code;
|
||||||
|
|
||||||
|
if (midCode == code) return ManufacturerTable[mid].name;
|
||||||
|
if (midCode < code) low = mid + 1;
|
||||||
|
else high = mid - 1;
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#ifndef _NMEA2KTWAI_H
|
#pragma once
|
||||||
#define _NMEA2KTWAI_H
|
|
||||||
#include "NMEA2000.h"
|
#include "NMEA2000.h"
|
||||||
// #include "GwTimer.h"
|
// #include "GwTimer.h"
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ public:
|
|||||||
ST_DISABLED,
|
ST_DISABLED,
|
||||||
ST_ERROR
|
ST_ERROR
|
||||||
} STATE;
|
} STATE;
|
||||||
typedef struct{
|
typedef struct {
|
||||||
//see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html#_CPPv418twai_status_info_t
|
//see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html#_CPPv418twai_status_info_t
|
||||||
uint32_t rx_errors = 0;
|
uint32_t rx_errors = 0;
|
||||||
uint32_t tx_errors = 0;
|
uint32_t tx_errors = 0;
|
||||||
@@ -31,10 +30,12 @@ public:
|
|||||||
static const char *stateStr(const STATE &st);
|
static const char *stateStr(const STATE &st);
|
||||||
virtual bool CANOpen();
|
virtual bool CANOpen();
|
||||||
virtual ~Nmea2kTwai() {};
|
virtual ~Nmea2kTwai() {};
|
||||||
// static const int LOG_ERR = 0;
|
|
||||||
// static const int LOG_INFO = 1;
|
struct ManufacturerEntry {
|
||||||
// static const int LOG_DEBUG = 2;
|
uint16_t code;
|
||||||
// static const int LOG_MSG = 3;
|
const char* name;
|
||||||
|
};
|
||||||
|
const char* GetManufacturerName(uint16_t code);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent=true);
|
virtual bool CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent=true);
|
||||||
@@ -44,19 +45,19 @@ protected:
|
|||||||
to change size of library send frame buffer size.
|
to change size of library send frame buffer size.
|
||||||
See e.g. NMEA2000_teensy.cpp. */
|
See e.g. NMEA2000_teensy.cpp. */
|
||||||
virtual void InitCANFrameBuffers();
|
virtual void InitCANFrameBuffers();
|
||||||
virtual void logDebug(int level,const char *fmt,...){}
|
// virtual void logDebug(int level,const char *fmt,...){}
|
||||||
|
static const ManufacturerEntry ManufacturerTable[];
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initDriver();
|
void initDriver();
|
||||||
bool startRecovery();
|
bool startRecovery();
|
||||||
bool checkRecovery();
|
bool checkRecovery();
|
||||||
Status logStatus();
|
Status logStatus();
|
||||||
gpio_num_t TxPin;
|
gpio_num_t TxPin;
|
||||||
gpio_num_t RxPin;
|
gpio_num_t RxPin;
|
||||||
uint32_t txTimeouts = 0;
|
uint32_t txTimeouts = 0;
|
||||||
// GwIntervalRunner timers;
|
// GwIntervalRunner timers;
|
||||||
bool disabled = false;
|
bool disabled = false;
|
||||||
unsigned long lastRecoveryStart=0;
|
unsigned long lastRecoveryStart = 0;
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
};
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ void print_n2k_devicelist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rename to: void sendSwitchBank()
|
// rename to: void sendSwitchBank()
|
||||||
void SendToN2K(uint8_t keycode) {
|
void send_switchbank(uint8_t keycode) {
|
||||||
tN2kMsg N2kMsg;
|
tN2kMsg N2kMsg;
|
||||||
tN2kBinaryStatus bankstatus;
|
tN2kBinaryStatus bankstatus;
|
||||||
N2kResetBinaryStatus(bankstatus);
|
N2kResetBinaryStatus(bankstatus);
|
||||||
@@ -575,9 +575,9 @@ void loop() {
|
|||||||
// Send key code to destination
|
// Send key code to destination
|
||||||
atariKeyclick();
|
atariKeyclick();
|
||||||
if ((event.pressType == ButtonPressType::SHORT)) {
|
if ((event.pressType == ButtonPressType::SHORT)) {
|
||||||
SendToN2K(keycode[event.buttonId]);
|
send_switchbank(keycode[event.buttonId]);
|
||||||
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
|
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
|
||||||
SendToN2K(longcode[event.buttonId]);
|
send_switchbank(longcode[event.buttonId]);
|
||||||
}
|
}
|
||||||
// Debug:
|
// Debug:
|
||||||
if ((event.buttonId == BUTTON_1)
|
if ((event.buttonId == BUTTON_1)
|
||||||
|
|||||||
@@ -153,8 +153,8 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 252,
|
"max": 255,
|
||||||
"description": "The number uf switch bank the keys belong to\nRange [0 .. 252] default 0",
|
"description": "The number uf switch bank the keys belong to\nRange [0 .. 255] default 0",
|
||||||
"category": "Keys"
|
"category": "Keys"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -280,21 +280,27 @@
|
|||||||
{
|
{
|
||||||
"name": "n2kDestA",
|
"name": "n2kDestA",
|
||||||
"label": "Destination A",
|
"label": "Destination A",
|
||||||
"type": "list",
|
"type": "dynlist",
|
||||||
|
"source": "devicelist",
|
||||||
|
"interval": "5000",
|
||||||
"description": "Destination device A",
|
"description": "Destination device A",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "n2kDestB",
|
"name": "n2kDestB",
|
||||||
"label": "Destination B",
|
"label": "Destination B",
|
||||||
"type": "list",
|
"type": "dynlist",
|
||||||
|
"source": "devicelist",
|
||||||
|
"interval": "5000",
|
||||||
"description": "Destination device B",
|
"description": "Destination device B",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "n2kDestC",
|
"name": "n2kDestC",
|
||||||
"label": "Destination C",
|
"label": "Destination C",
|
||||||
"type": "list",
|
"type": "dynlist",
|
||||||
|
"source": "devicelist",
|
||||||
|
"interval": "5000",
|
||||||
"description": "Destination device C",
|
"description": "Destination device C",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user