mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-27 12:46:34 +00:00
commit
74bb1a2eff
@ -29,7 +29,7 @@
|
|||||||
#include "AudioOutputI2S.h"
|
#include "AudioOutputI2S.h"
|
||||||
|
|
||||||
#if defined(ESP32) || defined(ESP8266)
|
#if defined(ESP32) || defined(ESP8266)
|
||||||
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
|
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll, uint8_t mult, uint32_t freq)
|
||||||
{
|
{
|
||||||
this->portNo = port;
|
this->portNo = port;
|
||||||
this->i2sOn = false;
|
this->i2sOn = false;
|
||||||
@ -49,6 +49,8 @@ AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int
|
|||||||
bclkPin = 26;
|
bclkPin = 26;
|
||||||
wclkPin = 25;
|
wclkPin = 25;
|
||||||
doutPin = 22;
|
doutPin = 22;
|
||||||
|
mcmult = mult;
|
||||||
|
mclk_freq = freq;
|
||||||
SetGain(1.0);
|
SetGain(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +86,6 @@ bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout, int mclk, int din)
|
|||||||
mclkPin = mclk;
|
mclkPin = mclk;
|
||||||
dinPin = din;
|
dinPin = din;
|
||||||
|
|
||||||
|
|
||||||
if (i2sOn)
|
if (i2sOn)
|
||||||
return SetPinout();
|
return SetPinout();
|
||||||
|
|
||||||
@ -238,10 +239,12 @@ bool AudioOutputI2S::begin(bool txDAC)
|
|||||||
.dma_buf_len = 128,
|
.dma_buf_len = 128,
|
||||||
.use_apll = use_apll, // Use audio PLL
|
.use_apll = use_apll, // Use audio PLL
|
||||||
.tx_desc_auto_clear = true,
|
.tx_desc_auto_clear = true,
|
||||||
.fixed_mclk = 0,
|
#ifdef ESP32
|
||||||
|
.fixed_mclk = (int)mclk_freq,
|
||||||
//.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
//.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_128,
|
.mclk_multiple = (i2s_mclk_multiple_t)mcmult,
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT,
|
#endif
|
||||||
|
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
|
||||||
};
|
};
|
||||||
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
|
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
|
||||||
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK)
|
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK)
|
||||||
|
@ -28,11 +28,15 @@
|
|||||||
#define I2S_PIN_NO_CHANGE -1
|
#define I2S_PIN_NO_CHANGE -1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#define I2S_MCLK_MULTIPLE_DEFAULT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
class AudioOutputI2S : public AudioOutput
|
class AudioOutputI2S : public AudioOutput
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
#if defined(ESP32) || defined(ESP8266)
|
#if defined(ESP32) || defined(ESP8266)
|
||||||
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
|
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE, uint8_t mult=I2S_MCLK_MULTIPLE_DEFAULT, uint32_t freq=0);
|
||||||
bool SetPinout(int bclkPin, int wclkPin, int doutPin, int mclk = I2S_PIN_NO_CHANGE, int din = I2S_PIN_NO_CHANGE);
|
bool SetPinout(int bclkPin, int wclkPin, int doutPin, int mclk = I2S_PIN_NO_CHANGE, int din = I2S_PIN_NO_CHANGE);
|
||||||
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
||||||
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
||||||
@ -69,6 +73,8 @@ class AudioOutputI2S : public AudioOutput
|
|||||||
uint8_t bclkPin;
|
uint8_t bclkPin;
|
||||||
uint8_t wclkPin;
|
uint8_t wclkPin;
|
||||||
uint8_t doutPin;
|
uint8_t doutPin;
|
||||||
uint8_t dinPin;
|
int8_t dinPin;
|
||||||
uint8_t mclkPin;
|
int8_t mclkPin;
|
||||||
|
uint8_t mcmult;
|
||||||
|
uint32_t mclk_freq;
|
||||||
};
|
};
|
||||||
|
@ -105,6 +105,16 @@ struct AUDIO_I2S {
|
|||||||
} audio_i2s;
|
} audio_i2s;
|
||||||
|
|
||||||
|
|
||||||
|
// because S3 box mclk severly disturbs WLAN
|
||||||
|
// we must slow down after each sound
|
||||||
|
#ifdef ESP32S3_BOX
|
||||||
|
#undef DOWNRATE
|
||||||
|
#define DOWNRATE audio_i2s.out->SetRate(1000);
|
||||||
|
#else
|
||||||
|
#undef DOWNRATE
|
||||||
|
#define DOWNRATE
|
||||||
|
#endif
|
||||||
|
|
||||||
#define MIC_CHANNELS 1
|
#define MIC_CHANNELS 1
|
||||||
|
|
||||||
#ifdef USE_TTGO_WATCH
|
#ifdef USE_TTGO_WATCH
|
||||||
@ -249,11 +259,19 @@ AudioGeneratorTalkie *talkie = nullptr;
|
|||||||
}
|
}
|
||||||
delete talkie;
|
delete talkie;
|
||||||
audio_i2s.out->stop();
|
audio_i2s.out->stop();
|
||||||
|
DOWNRATE
|
||||||
AUDIO_PWR_OFF
|
AUDIO_PWR_OFF
|
||||||
}
|
}
|
||||||
#endif // USE_I2S_SAY_TIME
|
#endif // USE_I2S_SAY_TIME
|
||||||
|
|
||||||
|
|
||||||
|
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
||||||
|
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#define I2S_MCLK_MULTIPLE_128 0
|
||||||
|
#endif
|
||||||
|
|
||||||
int32_t I2S_Init_0(void) {
|
int32_t I2S_Init_0(void) {
|
||||||
|
|
||||||
audio_i2s.i2s_port = (i2s_port_t)0;
|
audio_i2s.i2s_port = (i2s_port_t)0;
|
||||||
@ -280,7 +298,8 @@ int32_t I2S_Init_0(void) {
|
|||||||
#ifdef USE_I2S_NO_DAC
|
#ifdef USE_I2S_NO_DAC
|
||||||
audio_i2s.out = new AudioOutputI2SNoDAC();
|
audio_i2s.out = new AudioOutputI2SNoDAC();
|
||||||
#else
|
#else
|
||||||
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
|
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
|
||||||
|
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
|
||||||
#endif // USE_I2S_NO_DAC
|
#endif // USE_I2S_NO_DAC
|
||||||
audio_i2s.mclk = Pin(GPIO_I2S_MCLK);
|
audio_i2s.mclk = Pin(GPIO_I2S_MCLK);
|
||||||
audio_i2s.bclk = Pin(GPIO_I2S_BCLK);
|
audio_i2s.bclk = Pin(GPIO_I2S_BCLK);
|
||||||
@ -292,7 +311,8 @@ int32_t I2S_Init_0(void) {
|
|||||||
#ifdef USE_I2S_NO_DAC
|
#ifdef USE_I2S_NO_DAC
|
||||||
audio_i2s.out = new AudioOutputI2SNoDAC();
|
audio_i2s.out = new AudioOutputI2SNoDAC();
|
||||||
#else
|
#else
|
||||||
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
|
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
|
||||||
|
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
|
||||||
#endif // USE_I2S_NO_DAC
|
#endif // USE_I2S_NO_DAC
|
||||||
audio_i2s.mclk = Pin(GPIO_I2S_MCLK, 1);
|
audio_i2s.mclk = Pin(GPIO_I2S_MCLK, 1);
|
||||||
audio_i2s.bclk = Pin(GPIO_I2S_BCLK, 1);
|
audio_i2s.bclk = Pin(GPIO_I2S_BCLK, 1);
|
||||||
@ -304,7 +324,7 @@ int32_t I2S_Init_0(void) {
|
|||||||
}
|
}
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
// esp8266 have fixed pins
|
// esp8266 have fixed pins
|
||||||
if ((audio_i2s.bclk != 15) || (audio_i2s.ws != 2) || (audio_i2s.dout!= 3)) {
|
if ((audio_i2s.bclk != 15) || (audio_i2s.ws != 2) || (audio_i2s.dout != 3)) {
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
#endif // ESP8266
|
#endif // ESP8266
|
||||||
@ -312,11 +332,8 @@ int32_t I2S_Init_0(void) {
|
|||||||
|
|
||||||
audio_i2s.out->SetPinout(audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
|
audio_i2s.out->SetPinout(audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
|
||||||
|
|
||||||
AddLog(LOG_LEVEL_INFO, PSTR("Init audio I2S: bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
|
AddLog(LOG_LEVEL_INFO, PSTR("Init audio I2S: port=%d, bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), audio_i2s.i2s_port, audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
|
||||||
|
|
||||||
#if defined(ESP32) && defined(ESP32S3_BOX)
|
|
||||||
S3boxInit();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@ -330,12 +347,19 @@ int32_t I2S_Init_0(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void I2S_Init(void) {
|
void I2S_Init(void) {
|
||||||
|
|
||||||
|
#if defined(ESP32) && defined(ESP32S3_BOX)
|
||||||
|
S3boxInit();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (I2S_Init_0()) {
|
if (I2S_Init_0()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DOWNRATE
|
||||||
audio_i2s.is2_volume=10;
|
audio_i2s.is2_volume=10;
|
||||||
audio_i2s.out->SetGain(((float)audio_i2s.is2_volume/100.0)*4.0);
|
audio_i2s.out->SetGain(((float)audio_i2s.is2_volume/100.0)*4.0);
|
||||||
audio_i2s.out->stop();
|
audio_i2s.out->stop();
|
||||||
@ -383,6 +407,7 @@ uint32_t SpeakerMic(uint8_t spkr) {
|
|||||||
I2S_Init_0();
|
I2S_Init_0();
|
||||||
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
|
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
|
||||||
audio_i2s.out->stop();
|
audio_i2s.out->stop();
|
||||||
|
DOWNRATE
|
||||||
} else {
|
} else {
|
||||||
// config mic
|
// config mic
|
||||||
i2s_config_t i2s_config = {
|
i2s_config_t i2s_config = {
|
||||||
@ -398,7 +423,7 @@ uint32_t SpeakerMic(uint8_t spkr) {
|
|||||||
.use_apll = 0, // Use audio PLL
|
.use_apll = 0, // Use audio PLL
|
||||||
.tx_desc_auto_clear = true,
|
.tx_desc_auto_clear = true,
|
||||||
.fixed_mclk = 0,
|
.fixed_mclk = 0,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // I2S_MCLK_MULTIPLE_128
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
|
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -406,7 +431,16 @@ uint32_t SpeakerMic(uint8_t spkr) {
|
|||||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||||
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
|
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
|
||||||
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
||||||
#else
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_I2S_MIC
|
||||||
|
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX);
|
||||||
|
// mic select to GND
|
||||||
|
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||||
|
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_M5STACK_CORE2
|
||||||
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
|
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -632,6 +666,7 @@ void StopPlaying() {
|
|||||||
delete audio_i2s.ifile;
|
delete audio_i2s.ifile;
|
||||||
audio_i2s.ifile = NULL;
|
audio_i2s.ifile = NULL;
|
||||||
}
|
}
|
||||||
|
DOWNRATE
|
||||||
AUDIO_PWR_OFF
|
AUDIO_PWR_OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,16 +686,20 @@ void Cmd_WebRadio(void) {
|
|||||||
const char HTTP_WEBRADIO[] PROGMEM =
|
const char HTTP_WEBRADIO[] PROGMEM =
|
||||||
"{s}" "I2S_WR-Title" "{m}%s{e}";
|
"{s}" "I2S_WR-Title" "{m}%s{e}";
|
||||||
|
|
||||||
void I2S_WR_Show(void) {
|
void I2S_WR_Show(bool json) {
|
||||||
if (audio_i2s.decoder) {
|
if (audio_i2s.decoder) {
|
||||||
WSContentSend_PD(HTTP_WEBRADIO,audio_i2s.wr_title);
|
if (json) {
|
||||||
|
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), audio_i2s.wr_title);
|
||||||
|
} else {
|
||||||
|
WSContentSend_PD(HTTP_WEBRADIO,audio_i2s.wr_title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
|
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
|
|
||||||
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX)
|
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
|
||||||
void Cmd_MicRec(void) {
|
void Cmd_MicRec(void) {
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
uint16 time = 10;
|
uint16 time = 10;
|
||||||
@ -683,13 +722,8 @@ void Play_mp3(const char *path) {
|
|||||||
if (audio_i2s.decoder || audio_i2s.mp3) return;
|
if (audio_i2s.decoder || audio_i2s.mp3) return;
|
||||||
if (!audio_i2s.out) return;
|
if (!audio_i2s.out) return;
|
||||||
|
|
||||||
if (!ufsp->exists(path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool I2S_Task;
|
bool I2S_Task;
|
||||||
|
|
||||||
AUDIO_PWR_ON
|
|
||||||
if (*path=='+') {
|
if (*path=='+') {
|
||||||
I2S_Task = true;
|
I2S_Task = true;
|
||||||
path++;
|
path++;
|
||||||
@ -697,6 +731,12 @@ void Play_mp3(const char *path) {
|
|||||||
I2S_Task = false;
|
I2S_Task = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ufsp->exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AUDIO_PWR_ON
|
||||||
|
|
||||||
audio_i2s.file = new AudioFileSourceFS(*ufsp, path);
|
audio_i2s.file = new AudioFileSourceFS(*ufsp, path);
|
||||||
|
|
||||||
audio_i2s.id3 = new AudioFileSourceID3(audio_i2s.file);
|
audio_i2s.id3 = new AudioFileSourceID3(audio_i2s.file);
|
||||||
@ -730,6 +770,7 @@ void mp3_delete(void) {
|
|||||||
delete audio_i2s.id3;
|
delete audio_i2s.id3;
|
||||||
delete audio_i2s.mp3;
|
delete audio_i2s.mp3;
|
||||||
audio_i2s.mp3=nullptr;
|
audio_i2s.mp3=nullptr;
|
||||||
|
DOWNRATE
|
||||||
AUDIO_PWR_OFF
|
AUDIO_PWR_OFF
|
||||||
}
|
}
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
@ -746,6 +787,7 @@ void Say(char *text) {
|
|||||||
delete sam;
|
delete sam;
|
||||||
audio_i2s.out->stop();
|
audio_i2s.out->stop();
|
||||||
|
|
||||||
|
DOWNRATE
|
||||||
AUDIO_PWR_OFF
|
AUDIO_PWR_OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +799,7 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
|||||||
#ifdef USE_I2S_WEBRADIO
|
#ifdef USE_I2S_WEBRADIO
|
||||||
"|WR"
|
"|WR"
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX)
|
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
|
||||||
"|REC"
|
"|REC"
|
||||||
#endif // USE_M5STACK_CORE2
|
#endif // USE_M5STACK_CORE2
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
@ -770,7 +812,7 @@ void (* const I2SAudio_Command[])(void) PROGMEM = {
|
|||||||
#ifdef USE_I2S_WEBRADIO
|
#ifdef USE_I2S_WEBRADIO
|
||||||
,&Cmd_WebRadio
|
,&Cmd_WebRadio
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX)
|
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
|
||||||
,&Cmd_MicRec
|
,&Cmd_MicRec
|
||||||
#endif // USE_M5STACK_CORE2
|
#endif // USE_M5STACK_CORE2
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
@ -826,10 +868,15 @@ bool Xdrv42(uint8_t function) {
|
|||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
#ifdef USE_I2S_WEBRADIO
|
#ifdef USE_I2S_WEBRADIO
|
||||||
case FUNC_WEB_SENSOR:
|
case FUNC_WEB_SENSOR:
|
||||||
I2S_WR_Show();
|
I2S_WR_Show(false);
|
||||||
break;
|
break;
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
|
#ifdef USE_I2S_WEBRADIO
|
||||||
|
case FUNC_JSON_APPEND:
|
||||||
|
I2S_WR_Show(true);
|
||||||
|
break;
|
||||||
|
#endif // USE_I2S_WEBRADIO
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user