3.3.7
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* BTAddress.cpp
|
||||
*
|
||||
* Created on: Jul 2, 2017
|
||||
* Author: kolban
|
||||
* Ported on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
|
||||
#include "BTAddress.h"
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "esp32-hal-log.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create an address from the native ESP32 representation.
|
||||
* @param [in] address The native representation.
|
||||
*/
|
||||
BTAddress::BTAddress(esp_bd_addr_t address) {
|
||||
memcpy(m_address, address, ESP_BD_ADDR_LEN);
|
||||
} // BTAddress
|
||||
|
||||
BTAddress::BTAddress() {
|
||||
bzero(m_address, ESP_BD_ADDR_LEN);
|
||||
} // BTAddress
|
||||
|
||||
/**
|
||||
* @brief Create an address from a hex string
|
||||
*
|
||||
* A hex string is of the format:
|
||||
* ```
|
||||
* 00:00:00:00:00:00
|
||||
* ```
|
||||
* which is 17 characters in length.
|
||||
*
|
||||
* @param [in] stringAddress The hex representation of the address.
|
||||
*/
|
||||
BTAddress::BTAddress(String stringAddress) {
|
||||
if (stringAddress.length() != 17) {
|
||||
return;
|
||||
}
|
||||
|
||||
int data[6];
|
||||
sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]);
|
||||
m_address[0] = (uint8_t)data[0];
|
||||
m_address[1] = (uint8_t)data[1];
|
||||
m_address[2] = (uint8_t)data[2];
|
||||
m_address[3] = (uint8_t)data[3];
|
||||
m_address[4] = (uint8_t)data[4];
|
||||
m_address[5] = (uint8_t)data[5];
|
||||
} // BTAddress
|
||||
|
||||
/**
|
||||
* @brief Determine if this address equals another.
|
||||
* @param [in] otherAddress The other address to compare against.
|
||||
* @return True if the addresses are equal.
|
||||
*/
|
||||
bool BTAddress::equals(BTAddress otherAddress) {
|
||||
return memcmp(otherAddress.getNative(), m_address, 6) == 0;
|
||||
} // equals
|
||||
|
||||
BTAddress::operator bool() const {
|
||||
for (int i = 0; i < ESP_BD_ADDR_LEN; i++) {
|
||||
if (this->m_address[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} // operator ()
|
||||
|
||||
/**
|
||||
* @brief Return the native representation of the address.
|
||||
* @return The native representation of the address.
|
||||
*/
|
||||
esp_bd_addr_t *BTAddress::getNative() const {
|
||||
return const_cast<esp_bd_addr_t *>(&m_address);
|
||||
} // getNative
|
||||
|
||||
/**
|
||||
* @brief Convert a BT address to a string.
|
||||
* @param [in] capital changes the letter size
|
||||
* By default the parameter `capital` == false and the string representation of an address is in the format:
|
||||
* ```
|
||||
* xx:xx:xx:xx:xx:xx
|
||||
* ```
|
||||
* When the parameter `capital` == true the format uses capital letters:
|
||||
* ```
|
||||
* XX:XX:XX:XX:XX:XX
|
||||
* ```
|
||||
* @return The string representation of the address.
|
||||
*/
|
||||
String BTAddress::toString(bool capital) const {
|
||||
auto size = 18;
|
||||
char *res = (char *)malloc(size);
|
||||
if (capital) {
|
||||
snprintf(res, size, "%02X:%02X:%02X:%02X:%02X:%02X", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]);
|
||||
} else {
|
||||
snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]);
|
||||
}
|
||||
String ret(res);
|
||||
free(res);
|
||||
return ret;
|
||||
} // toString
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* BTAddress.h
|
||||
*
|
||||
* Created on: Jul 2, 2017
|
||||
* Author: kolban
|
||||
* Ported on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
|
||||
#ifndef COMPONENTS_CPP_UTILS_BTADDRESS_H_
|
||||
#define COMPONENTS_CPP_UTILS_BTADDRESS_H_
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
#include <esp_gap_bt_api.h> // ESP32 BT
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* @brief A %BT device address.
|
||||
*
|
||||
* Every %BT device has a unique address which can be used to identify it and form connections.
|
||||
*/
|
||||
class BTAddress {
|
||||
public:
|
||||
BTAddress();
|
||||
BTAddress(esp_bd_addr_t address);
|
||||
BTAddress(String stringAddress);
|
||||
bool equals(BTAddress otherAddress);
|
||||
operator bool() const;
|
||||
|
||||
esp_bd_addr_t *getNative() const;
|
||||
String toString(bool capital = false) const;
|
||||
|
||||
private:
|
||||
esp_bd_addr_t m_address;
|
||||
};
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED */
|
||||
#endif /* COMPONENTS_CPP_UTILS_BTADDRESS_H_ */
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* BTAdvertisedDevice.h
|
||||
*
|
||||
* Created on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
#include "BTAddress.h"
|
||||
#include <string>
|
||||
|
||||
class BTAdvertisedDevice {
|
||||
public:
|
||||
virtual ~BTAdvertisedDevice() = default;
|
||||
|
||||
virtual BTAddress getAddress() = 0;
|
||||
virtual uint32_t getCOD() const = 0;
|
||||
virtual std::string getName() const = 0;
|
||||
virtual int8_t getRSSI() const = 0;
|
||||
|
||||
virtual bool haveCOD() const = 0;
|
||||
virtual bool haveName() const = 0;
|
||||
virtual bool haveRSSI() const = 0;
|
||||
|
||||
virtual std::string toString() = 0;
|
||||
};
|
||||
|
||||
class BTAdvertisedDeviceSet : public virtual BTAdvertisedDevice {
|
||||
public:
|
||||
BTAdvertisedDeviceSet();
|
||||
//~BTAdvertisedDeviceSet() = default;
|
||||
|
||||
BTAddress getAddress();
|
||||
uint32_t getCOD() const;
|
||||
std::string getName() const;
|
||||
int8_t getRSSI() const;
|
||||
|
||||
bool haveCOD() const;
|
||||
bool haveName() const;
|
||||
bool haveRSSI() const;
|
||||
|
||||
std::string toString();
|
||||
|
||||
void setAddress(BTAddress address);
|
||||
void setCOD(uint32_t cod);
|
||||
void setName(std::string name);
|
||||
void setRSSI(int8_t rssi);
|
||||
|
||||
bool m_haveCOD;
|
||||
bool m_haveName;
|
||||
bool m_haveRSSI;
|
||||
|
||||
BTAddress m_address = BTAddress((uint8_t *)"\0\0\0\0\0\0");
|
||||
uint32_t m_cod;
|
||||
std::string m_name;
|
||||
int8_t m_rssi;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* BTAdvertisedDeviceSet.cpp
|
||||
*
|
||||
* Created on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
|
||||
//#include <map>
|
||||
|
||||
#include "BTAdvertisedDevice.h"
|
||||
//#include "BTScan.h"
|
||||
|
||||
BTAdvertisedDeviceSet::BTAdvertisedDeviceSet() {
|
||||
m_cod = 0;
|
||||
m_name = "";
|
||||
m_rssi = 0;
|
||||
|
||||
m_haveCOD = false;
|
||||
m_haveName = false;
|
||||
m_haveRSSI = false;
|
||||
} // BTAdvertisedDeviceSet
|
||||
|
||||
BTAddress BTAdvertisedDeviceSet::getAddress() {
|
||||
return m_address;
|
||||
}
|
||||
uint32_t BTAdvertisedDeviceSet::getCOD() const {
|
||||
return m_cod;
|
||||
}
|
||||
std::string BTAdvertisedDeviceSet::getName() const {
|
||||
return m_name;
|
||||
}
|
||||
int8_t BTAdvertisedDeviceSet::getRSSI() const {
|
||||
return m_rssi;
|
||||
}
|
||||
|
||||
bool BTAdvertisedDeviceSet::haveCOD() const {
|
||||
return m_haveCOD;
|
||||
}
|
||||
bool BTAdvertisedDeviceSet::haveName() const {
|
||||
return m_haveName;
|
||||
}
|
||||
bool BTAdvertisedDeviceSet::haveRSSI() const {
|
||||
return m_haveRSSI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a string representation of this device.
|
||||
* @return A string representation of this device.
|
||||
*/
|
||||
std::string BTAdvertisedDeviceSet::toString() {
|
||||
std::string res = "Name: " + getName() + ", Address: " + std::string(getAddress().toString().c_str(), getAddress().toString().length());
|
||||
if (haveCOD()) {
|
||||
char val[7]; //6 hex digits + null
|
||||
snprintf(val, sizeof(val), "%06lx", getCOD() & 0xFFFFFF);
|
||||
res += ", cod: 0x";
|
||||
res += val;
|
||||
}
|
||||
if (haveRSSI()) {
|
||||
char val[6];
|
||||
snprintf(val, sizeof(val), "%d", (int8_t)getRSSI());
|
||||
res += ", rssi: ";
|
||||
res += val;
|
||||
}
|
||||
return res;
|
||||
} // toString
|
||||
|
||||
void BTAdvertisedDeviceSet::setAddress(BTAddress address) {
|
||||
m_address = address;
|
||||
}
|
||||
|
||||
void BTAdvertisedDeviceSet::setCOD(uint32_t cod) {
|
||||
m_cod = cod;
|
||||
m_haveCOD = true;
|
||||
}
|
||||
|
||||
void BTAdvertisedDeviceSet::setName(std::string name) {
|
||||
m_name = name;
|
||||
m_haveName = true;
|
||||
}
|
||||
|
||||
void BTAdvertisedDeviceSet::setRSSI(int8_t rssi) {
|
||||
m_rssi = rssi;
|
||||
m_haveRSSI = true;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BT_ENABLED */
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* BTScan.h
|
||||
*
|
||||
* Created on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <Print.h>
|
||||
#include "BTAddress.h"
|
||||
#include "BTAdvertisedDevice.h"
|
||||
|
||||
class BTAdvertisedDevice;
|
||||
class BTAdvertisedDeviceSet;
|
||||
|
||||
class BTScanResults {
|
||||
public:
|
||||
virtual ~BTScanResults() = default;
|
||||
|
||||
virtual void dump(Print *print = nullptr) = 0;
|
||||
virtual int getCount() = 0;
|
||||
virtual BTAdvertisedDevice *getDevice(int i) = 0;
|
||||
};
|
||||
|
||||
class BTScanResultsSet : public BTScanResults {
|
||||
public:
|
||||
void dump(Print *print = nullptr);
|
||||
int getCount();
|
||||
BTAdvertisedDevice *getDevice(int i);
|
||||
|
||||
bool add(BTAdvertisedDeviceSet advertisedDevice, bool unique = true);
|
||||
void clear();
|
||||
|
||||
std::map<std::string, BTAdvertisedDeviceSet> m_vectorAdvertisedDevices;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* BTScanResultsSet.cpp
|
||||
*
|
||||
* Created on: Feb 5, 2021
|
||||
* Author: Thomas M. (ArcticSnowSky)
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
|
||||
#include <esp_err.h>
|
||||
|
||||
#include "BTAdvertisedDevice.h"
|
||||
#include "BTScan.h"
|
||||
//#include "GeneralUtils.h"
|
||||
#include "esp32-hal-log.h"
|
||||
|
||||
class BTAdvertisedDevice;
|
||||
|
||||
/**
|
||||
* @brief Dump the scan results to the log.
|
||||
*/
|
||||
void BTScanResultsSet::dump(Print *print) {
|
||||
int cnt = getCount();
|
||||
if (print == nullptr) {
|
||||
log_v(">> Dump scan results : %d", cnt);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
BTAdvertisedDevice *dev = getDevice(i);
|
||||
if (dev) {
|
||||
log_d("- %d: %s\n", i + 1, dev->toString().c_str());
|
||||
} else {
|
||||
log_d("- %d is null\n", i + 1);
|
||||
}
|
||||
}
|
||||
log_v("-- dump finished --");
|
||||
} else {
|
||||
print->printf(">> Dump scan results: %d\n", cnt);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
BTAdvertisedDevice *dev = getDevice(i);
|
||||
if (dev) {
|
||||
print->printf("- %d: %s\n", i + 1, dev->toString().c_str());
|
||||
} else {
|
||||
print->printf("- %d is null\n", i + 1);
|
||||
}
|
||||
}
|
||||
print->println("-- Dump finished --");
|
||||
}
|
||||
} // dump
|
||||
|
||||
/**
|
||||
* @brief Return the count of devices found in the last scan.
|
||||
* @return The number of devices found in the last scan.
|
||||
*/
|
||||
int BTScanResultsSet::getCount() {
|
||||
return m_vectorAdvertisedDevices.size();
|
||||
} // getCount
|
||||
|
||||
/**
|
||||
* @brief Return the specified device at the given index.
|
||||
* The index should be between 0 and getCount()-1.
|
||||
* @param [in] i The index of the device.
|
||||
* @return The device at the specified index.
|
||||
*/
|
||||
BTAdvertisedDevice *BTScanResultsSet::getDevice(int i) {
|
||||
if (i < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
BTAdvertisedDeviceSet *pDev = &m_vectorAdvertisedDevices.begin()->second;
|
||||
for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) {
|
||||
pDev = &it->second;
|
||||
if (x == i) {
|
||||
break;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
return x == i ? pDev : nullptr;
|
||||
}
|
||||
|
||||
void BTScanResultsSet::clear() {
|
||||
//for(auto _dev : m_vectorAdvertisedDevices)
|
||||
// delete _dev.second;
|
||||
m_vectorAdvertisedDevices.clear();
|
||||
}
|
||||
|
||||
bool BTScanResultsSet::add(BTAdvertisedDeviceSet advertisedDevice, bool unique) {
|
||||
std::string key = std::string(advertisedDevice.getAddress().toString().c_str(), advertisedDevice.getAddress().toString().length());
|
||||
if (!unique || m_vectorAdvertisedDevices.count(key) == 0) {
|
||||
m_vectorAdvertisedDevices.insert(std::pair<std::string, BTAdvertisedDeviceSet>(key, advertisedDevice));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
// Copyright 2018 Evandro Luis Copercini
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef _BLUETOOTH_SERIAL_H_
|
||||
#define _BLUETOOTH_SERIAL_H_
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_BT_SUPPORTED && defined(CONFIG_BT_ENABLED) && defined(CONFIG_BLUEDROID_ENABLED)
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Stream.h"
|
||||
#include <esp_gap_bt_api.h>
|
||||
#include <esp_spp_api.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include "BTScan.h"
|
||||
#include "BTAdvertisedDevice.h"
|
||||
|
||||
typedef std::function<void(const uint8_t *buffer, size_t size)> BluetoothSerialDataCb;
|
||||
typedef std::function<void(uint32_t num_val)> ConfirmRequestCb;
|
||||
typedef std::function<void()> KeyRequestCb;
|
||||
typedef std::function<void(boolean success)> AuthCompleteCb;
|
||||
typedef std::function<void(BTAdvertisedDevice *pAdvertisedDevice)> BTAdvertisedDeviceCb;
|
||||
|
||||
class [[deprecated("BluetoothSerial won't be supported in version 4.0.0 by default")]] BluetoothSerial : public Stream {
|
||||
public:
|
||||
BluetoothSerial(void);
|
||||
~BluetoothSerial(void);
|
||||
|
||||
bool begin(String localName = String(), bool isMaster = false, bool disableBLE = false);
|
||||
bool begin(unsigned long baud) { //compatibility
|
||||
return begin();
|
||||
}
|
||||
int available(void);
|
||||
int peek(void);
|
||||
bool hasClient(void);
|
||||
int read(void);
|
||||
size_t write(uint8_t c);
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
void flush();
|
||||
void end(void);
|
||||
void memrelease();
|
||||
void setTimeout(int timeoutMS);
|
||||
void onData(BluetoothSerialDataCb cb);
|
||||
esp_err_t register_callback(esp_spp_cb_t callback);
|
||||
|
||||
void onConfirmRequest(ConfirmRequestCb cb);
|
||||
void onKeyRequest(KeyRequestCb cb);
|
||||
void respondPasskey(uint32_t passkey);
|
||||
void onAuthComplete(AuthCompleteCb cb);
|
||||
void confirmReply(boolean confirm);
|
||||
|
||||
void enableSSP();
|
||||
void enableSSP(bool inputCapability, bool outputCapability);
|
||||
void disableSSP();
|
||||
bool setPin(const char *pin, uint8_t pin_code_len);
|
||||
bool connect(String remoteName);
|
||||
bool connect(
|
||||
uint8_t remoteAddress[], int channel = 0, esp_spp_sec_t sec_mask = (ESP_SPP_SEC_ENCRYPT | ESP_SPP_SEC_AUTHENTICATE),
|
||||
esp_spp_role_t role = ESP_SPP_ROLE_MASTER
|
||||
);
|
||||
bool connect(
|
||||
const BTAddress &remoteAddress, int channel = 0, esp_spp_sec_t sec_mask = (ESP_SPP_SEC_ENCRYPT | ESP_SPP_SEC_AUTHENTICATE),
|
||||
esp_spp_role_t role = ESP_SPP_ROLE_MASTER
|
||||
) {
|
||||
return connect(*remoteAddress.getNative(), channel, sec_mask);
|
||||
};
|
||||
bool connect();
|
||||
bool connected(int timeout = 0);
|
||||
bool isClosed();
|
||||
bool isReady(bool checkMaster = false, int timeout = 0);
|
||||
bool disconnect();
|
||||
bool unpairDevice(uint8_t remoteAddress[]);
|
||||
|
||||
BTScanResults *discover(int timeout = 0x30 * 1280);
|
||||
bool discoverAsync(BTAdvertisedDeviceCb cb, int timeout = 0x30 * 1280);
|
||||
void discoverAsyncStop();
|
||||
void discoverClear();
|
||||
BTScanResults *getScanResults();
|
||||
|
||||
std::map<int, std::string> getChannels(const BTAddress &remoteAddress);
|
||||
|
||||
const int INQ_TIME = 1280; // Inquire Time unit 1280 ms
|
||||
const int MIN_INQ_TIME = (ESP_BT_GAP_MIN_INQ_LEN * INQ_TIME);
|
||||
const int MAX_INQ_TIME = (ESP_BT_GAP_MAX_INQ_LEN * INQ_TIME);
|
||||
|
||||
operator bool() const;
|
||||
void getBtAddress(uint8_t *mac);
|
||||
BTAddress getBtAddressObject();
|
||||
String getBtAddressString();
|
||||
//void dropCache(); // To be replaced
|
||||
void requestRemoteName(uint8_t *remoteAddress);
|
||||
bool readRemoteName(char rmt_name[ESP_BT_GAP_MAX_BDNAME_LEN + 1]);
|
||||
void invalidateRemoteName();
|
||||
int getNumberOfBondedDevices();
|
||||
int getBondedDevices(uint dev_num, esp_bd_addr_t *dev_list);
|
||||
bool deleteBondedDevice(uint8_t *remoteAddress);
|
||||
void deleteAllBondedDevices();
|
||||
|
||||
private:
|
||||
String local_name;
|
||||
int timeoutTicks = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user