cleanup, fix
This commit is contained in:
parent
787d92820c
commit
ab48dbf5e3
@ -11,8 +11,6 @@
|
||||
#include "storage.h"
|
||||
#include "SPIStorage.h"
|
||||
|
||||
#define JSON_CONFIG_FILE "/config.json"
|
||||
|
||||
class SDCard
|
||||
{
|
||||
private:
|
||||
@ -20,7 +18,7 @@ private:
|
||||
public:
|
||||
SDCard()
|
||||
{
|
||||
cardInitialized_ = initSDcard();
|
||||
cardInitialized_ = false;
|
||||
}
|
||||
|
||||
~SDCard()
|
||||
@ -29,42 +27,26 @@ public:
|
||||
SD_MMC.end();
|
||||
}
|
||||
|
||||
bool initSDcard()
|
||||
void SD2SPIStorage(SPIStorage* spifs)
|
||||
{
|
||||
if (cardInitialized_)
|
||||
return cardInitialized_;
|
||||
|
||||
bool oneBitMode = true;
|
||||
#if defined (SDMMC_D0) && defined (SDMMC_D1) && defined (SDMMC_D2) && defined (SDMMC_D3)
|
||||
if (SD_MMC.cardType() == CARD_NONE)
|
||||
SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0, SDMMC_D1, SDMMC_D2, SDMMC_D3);
|
||||
oneBitMode = false;
|
||||
#elif defined (SDMMC_D0) && !(defined (SDMMC_D1) && defined (SDMMC_D2) && defined (SDMMC_D3))
|
||||
if (SD_MMC.cardType() == CARD_NONE)
|
||||
SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0);
|
||||
#else
|
||||
Serial.println("SDCard: interface not available.");
|
||||
return false;
|
||||
#endif // dataPinsDefined
|
||||
|
||||
if ((!SD_MMC.begin("/sdcard", oneBitMode)) || (SD_MMC.cardType() == CARD_NONE))
|
||||
TSettings Settings;
|
||||
if (loadConfigFile(&Settings))
|
||||
{
|
||||
Serial.println("SDCard: No card found.");
|
||||
return false;
|
||||
spifs->saveConfigFile(&Settings);
|
||||
WiFi.begin(Settings.WifiSSID, Settings.WifiPW);
|
||||
Serial.println("SDCard: Settings transfered to internal memory. Restarting now.");
|
||||
ESP.restart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TSettings loadConfigFile()
|
||||
bool loadConfigFile(TSettings* Settings)
|
||||
{
|
||||
// Load existing configuration file
|
||||
// Read configuration from FS json
|
||||
Serial.println("SDCard: Mounting File System...");
|
||||
TSettings Settings;
|
||||
|
||||
if (initSDcard())
|
||||
{
|
||||
Serial.println("SDCard: Mounted");
|
||||
if (SD_MMC.exists(JSON_CONFIG_FILE))
|
||||
{
|
||||
// The file exists, reading and loading
|
||||
@ -77,16 +59,20 @@ public:
|
||||
DeserializationError error = deserializeJson(json, configFile);
|
||||
configFile.close();
|
||||
serializeJsonPretty(json, Serial);
|
||||
Serial.print('\n');
|
||||
if (!error)
|
||||
{
|
||||
Serial.println("SDCard: Parsing JSON");
|
||||
strcpy(Settings.WifiSSID, json["SSID"]);
|
||||
strcpy(Settings.WifiPW, json["Password"]);
|
||||
strcpy(Settings.PoolAddress, json["PoolURL"]);
|
||||
strcpy(Settings.BtcWallet, json["WalletID"]);
|
||||
Settings.PoolPort = json["Port"].as<int>();
|
||||
Settings.Timezone = json["Timezone"].as<int>();
|
||||
Settings.holdsData = true;
|
||||
strcpy(Settings->WifiSSID, json[JSON_KEY_SSID] | Settings->WifiSSID);
|
||||
strcpy(Settings->WifiPW, json[JSON_KEY_PASW] | Settings->WifiPW);
|
||||
strcpy(Settings->PoolAddress, json[JSON_KEY_POOLURL] | Settings->PoolAddress);
|
||||
strcpy(Settings->BtcWallet, json[JSON_KEY_WALLETID] | Settings->BtcWallet);
|
||||
if (json.containsKey(JSON_KEY_POOLPORT))
|
||||
Settings->PoolPort = json[JSON_KEY_POOLPORT].as<int>();
|
||||
if (json.containsKey(JSON_KEY_TIMEZONE))
|
||||
Settings->Timezone = json[JSON_KEY_TIMEZONE].as<int>();
|
||||
SD_MMC.end();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -94,26 +80,54 @@ public:
|
||||
Serial.println("SDCard: Failed to load json config");
|
||||
}
|
||||
}
|
||||
SD_MMC.end();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error mounting file system
|
||||
Serial.println("SDCard: Failed to mount.");
|
||||
Serial.println("SDCard: No config file available!");
|
||||
}
|
||||
return Settings;
|
||||
SD_MMC.end();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SD2SPIStorage(SPIStorage* spifs)
|
||||
private:
|
||||
|
||||
bool initSDcard()
|
||||
{
|
||||
TSettings Settings = loadConfigFile();
|
||||
if (Settings.holdsData)
|
||||
if((cardInitialized_)&&(SD_MMC.cardType() != CARD_NONE))
|
||||
{
|
||||
spifs->saveConfigFile(&Settings);
|
||||
WiFi.begin(Settings.WifiSSID, Settings.WifiPW);
|
||||
Serial.println("SDCard: Settings transfered to internal memory. Restarting now.");
|
||||
ESP.restart();
|
||||
Serial.println("SDCard: Already mounted.");
|
||||
return cardInitialized_;
|
||||
}
|
||||
|
||||
bool oneBitMode = true;
|
||||
#if defined (SDMMC_D0) && defined (SDMMC_D1) && defined (SDMMC_D2) && defined (SDMMC_D3)
|
||||
if (SD_MMC.cardType() == CARD_NONE)
|
||||
{
|
||||
SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0, SDMMC_D1, SDMMC_D2, SDMMC_D3);
|
||||
oneBitMode = false;
|
||||
Serial.println("SDCard: 4-Bit Mode.");
|
||||
}
|
||||
#elif defined (SDMMC_D0) && !(defined (SDMMC_D1) && defined (SDMMC_D2) && defined (SDMMC_D3))
|
||||
if (SD_MMC.cardType() == CARD_NONE)
|
||||
{
|
||||
SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0);
|
||||
Serial.println("SDCard: 1-Bit Mode.");
|
||||
}
|
||||
#else
|
||||
Serial.println("SDCard: interface not available.");
|
||||
return false;
|
||||
#endif // dataPinsDefined
|
||||
cardInitialized_ = SD_MMC.begin("/sdcard", oneBitMode);
|
||||
if ((cardInitialized_) && (SD_MMC.cardType() != CARD_NONE))
|
||||
{
|
||||
Serial.println("SDCard: Card mounted.");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("SDCard: No card found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -12,9 +12,6 @@
|
||||
#include "..\drivers.h"
|
||||
#include "storage.h"
|
||||
|
||||
// JSON configuration file
|
||||
#define JSON_CONFIG_FILE "/config.json"
|
||||
|
||||
class SPIStorage
|
||||
{
|
||||
private:
|
||||
@ -22,25 +19,28 @@ private:
|
||||
public:
|
||||
SPIStorage()
|
||||
{
|
||||
SPIFFSInitialized_ = init();
|
||||
SPIFFSInitialized_ = false;
|
||||
}
|
||||
|
||||
~SPIStorage()
|
||||
{
|
||||
if (SPIFFSInitialized_)
|
||||
SPIFFS.end();
|
||||
};
|
||||
|
||||
void saveConfigFile(TSettings*Settings)
|
||||
{
|
||||
if (init())
|
||||
{
|
||||
// Save Config in JSON format
|
||||
Serial.println(F("SPIFS: Saving configuration..."));
|
||||
|
||||
// Create a JSON document
|
||||
StaticJsonDocument<512> json;
|
||||
json["poolString"] = Settings->PoolAddress;
|
||||
json["portNumber"] = Settings->PoolPort;
|
||||
json["btcString"] = Settings->BtcWallet;
|
||||
json["gmtZone"] = Settings->Timezone;
|
||||
json[JSON_KEY_POOLURL] = Settings->PoolAddress;
|
||||
json[JSON_KEY_POOLPORT] = Settings->PoolPort;
|
||||
json[JSON_KEY_WALLETID] = Settings->BtcWallet;
|
||||
json[JSON_KEY_TIMEZONE] = Settings->Timezone;
|
||||
|
||||
// Open config file
|
||||
File configFile = SPIFFS.open(JSON_CONFIG_FILE, "w");
|
||||
@ -52,6 +52,7 @@ public:
|
||||
|
||||
// Serialize JSON data to write to file
|
||||
serializeJsonPretty(json, Serial);
|
||||
Serial.print('\n');
|
||||
if (serializeJson(json, configFile) == 0)
|
||||
{
|
||||
// Error writing file
|
||||
@ -59,28 +60,19 @@ public:
|
||||
}
|
||||
// Close file
|
||||
configFile.close();
|
||||
};
|
||||
}
|
||||
|
||||
bool init()
|
||||
bool loadConfigFile(TSettings* Settings)
|
||||
{
|
||||
if (SPIFFSInitialized_)
|
||||
return SPIFFSInitialized_;
|
||||
return SPIFFS.begin(false) || SPIFFS.begin(true);
|
||||
};
|
||||
|
||||
TSettings loadConfigFile()
|
||||
{
|
||||
// Load existing configuration file
|
||||
// Uncomment if we need to format filesystem
|
||||
// SPIFFS.format();
|
||||
|
||||
// Load existing configuration file
|
||||
// Read configuration from FS json
|
||||
Serial.println("SPIFS: Mounting File System...");
|
||||
TSettings Settings;
|
||||
// May need to make it begin(true) first time you are using SPIFFS
|
||||
if ((SPIFFSInitialized_)||(init()))
|
||||
|
||||
if (init())
|
||||
{
|
||||
Serial.println("SPIFS: Mounted");
|
||||
if (SPIFFS.exists(JSON_CONFIG_FILE))
|
||||
{
|
||||
// The file exists, reading and loading
|
||||
@ -93,15 +85,17 @@ public:
|
||||
DeserializationError error = deserializeJson(json, configFile);
|
||||
configFile.close();
|
||||
serializeJsonPretty(json, Serial);
|
||||
Serial.print('\n');
|
||||
if (!error)
|
||||
{
|
||||
Serial.println("SPIFS: Parsing JSON");
|
||||
|
||||
strcpy(Settings.PoolAddress, json["poolString"]);
|
||||
strcpy(Settings.BtcWallet, json["btcString"]);
|
||||
Settings.PoolPort = json["portNumber"].as<int>();
|
||||
Settings.Timezone = json["gmtZone"].as<int>();
|
||||
Settings.holdsData = true;
|
||||
strcpy(Settings->PoolAddress, json[JSON_KEY_POOLURL] | Settings->PoolAddress);
|
||||
strcpy(Settings->BtcWallet, json[JSON_KEY_WALLETID] | Settings->BtcWallet);
|
||||
if(json.containsKey(JSON_KEY_POOLPORT))
|
||||
Settings->PoolPort = json[JSON_KEY_POOLPORT].as<int>();
|
||||
if (json.containsKey(JSON_KEY_TIMEZONE))
|
||||
Settings->Timezone = json[JSON_KEY_TIMEZONE].as<int>();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -111,12 +105,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Error mounting file system
|
||||
Serial.println("SPIFS: Failed to mount.");
|
||||
}
|
||||
return Settings;
|
||||
return false;
|
||||
}
|
||||
|
||||
void deleteConfigFile()
|
||||
@ -124,6 +113,22 @@ public:
|
||||
Serial.println("SPIFS: Erasing config file..");
|
||||
SPIFFS.remove(JSON_CONFIG_FILE); //Borramos fichero
|
||||
}
|
||||
private:
|
||||
bool init()
|
||||
{
|
||||
if (!SPIFFSInitialized_)
|
||||
{
|
||||
Serial.println("SPIFS: Mounting File System...");
|
||||
// May need to make it begin(true) first time you are using SPIFFS
|
||||
SPIFFSInitialized_ = SPIFFS.begin(false) || SPIFFS.begin(true);
|
||||
SPIFFSInitialized_ ? Serial.println("SPIFS: Mounted") : Serial.println("SPIFS: Mounting failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("SPIFS: Already Mounted");
|
||||
}
|
||||
return SPIFFSInitialized_;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // _SPISTORAGE_H_
|
||||
|
@ -1,100 +0,0 @@
|
||||
|
||||
#ifndef _UARTCFGUI_H_
|
||||
#define _UARTCFGUI_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "storage.h"
|
||||
#include "SPIStorage.h"
|
||||
|
||||
class SerialCfgUI
|
||||
{
|
||||
public:
|
||||
SerialCfgUI()
|
||||
{
|
||||
#ifdef MONITOR_SPEED
|
||||
Serial.begin(MONITOR_SPEED);
|
||||
#else
|
||||
Serial.begin(115200);
|
||||
#endif //MONITOR_SPEED
|
||||
}
|
||||
|
||||
void UARTUI2SPIStorage(SPIStorage* spifs)
|
||||
{
|
||||
TSettings Settings;
|
||||
|
||||
if ((spifs->loadConfigFile(&Settings)) && StartUI(&Settings))
|
||||
{
|
||||
spifs->saveConfigFile(&Settings);
|
||||
WiFi.begin(Settings.WifiSSID, Settings.WifiPW);
|
||||
Serial.println("SerialUI: Settings transfered to internal memory. Restarting now.");
|
||||
ESP.restart();
|
||||
}
|
||||
Serial.println("SerialUI: not started.");
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool StartUI(TSettings* Settings)
|
||||
{
|
||||
Serial.println("Welcome to Nerdminer v2 serial config Interface.");
|
||||
Serial.println("Fill out the form to set up your Nerdminer.");
|
||||
Serial.println("Press 'Enter' to save your input or the default value.");
|
||||
|
||||
strcpy(Settings->WifiSSID, readEntry<const char*>("Enter Wifi SSID:", Settings->WifiSSID));
|
||||
strcpy(Settings->WifiPW, readEntry<const char*>("Enter Wifi Password:", Settings->WifiPW));
|
||||
strcpy(Settings->PoolAddress, readEntry<const char*>("Enter Pool Address:", Settings->PoolAddress));
|
||||
Settings->PoolPort = readEntry<int>("Enter Pool Port:", Settings->PoolPort);
|
||||
strcpy(Settings->BtcWallet, readEntry<const char*>("Enter your BTC Wallet ID:", Settings->BtcWallet));
|
||||
Settings->Timezone = readEntry<int>("Enter your Timezone (UTC+-12):", Settings->Timezone);
|
||||
|
||||
Serial.println("Setup complete.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T readEntry(const char* message = "newEntry:", T defaultvalue = "", const char delimieter = '\n')
|
||||
requires(
|
||||
(std::is_same_v<T, const char*>)
|
||||
|| (std::is_same_v<T, char*>)
|
||||
|| (std::is_same_v<T, int>)
|
||||
|| (std::is_same_v<T, double>)
|
||||
|| (std::is_same_v<T, float>)
|
||||
|| (std::is_same_v<T, String>))
|
||||
{
|
||||
Serial.println(message);
|
||||
|
||||
if(!String(defaultvalue).isEmpty())
|
||||
{
|
||||
Serial.print("Default Value: >");
|
||||
Serial.print(defaultvalue);
|
||||
Serial.println("<");
|
||||
};
|
||||
String value = Serial.readStringUntil(delimieter);
|
||||
value.trim();
|
||||
while((value.length() > 0) && value.endsWith(String('\r')))
|
||||
value.remove(value.length()-1);
|
||||
value.trim();
|
||||
if (value.length() > 0)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, String>)
|
||||
return value;
|
||||
else if constexpr ((std::is_same_v<T, const char*>)|| (std::is_same_v<T, char*>))
|
||||
return value.c_str();
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
return value.toInt();
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
return value.toDouble();
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
return value.toFloat();
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultvalue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // _UARTCFGUI_H_
|
@ -1,16 +1,53 @@
|
||||
#ifndef _STORAGE_H_
|
||||
#define _STORAGE_H_
|
||||
|
||||
#define DEFAULT_SSID "NMAP"
|
||||
#define DEFAULT_WIFIPW "1234567890"
|
||||
#define DEFAULT_POOLURL "public-pool.io"
|
||||
#define DEFAULT_WALLETID "yourBtcAddress"
|
||||
#define DEFAULT_POOLPORT 21496
|
||||
#define DEFAULT_TIMEZONE 2
|
||||
|
||||
// JSON config file
|
||||
#define JSON_CONFIG_FILE "/config.json"
|
||||
#define JSON_KEY_SSID "SSID"
|
||||
#define JSON_KEY_PASW "PW"
|
||||
#define JSON_KEY_POOLURL "PoolUrl"
|
||||
#define JSON_KEY_WALLETID "BtcWallet"
|
||||
#define JSON_KEY_POOLPORT "PoolPort"
|
||||
#define JSON_KEY_TIMEZONE "Timezone"
|
||||
|
||||
class TSettings
|
||||
{
|
||||
public:
|
||||
char WifiSSID[80]{ "sA51" };
|
||||
char WifiPW[80]{ "0000" };
|
||||
char PoolAddress[80]{ "public-pool.io" };
|
||||
char BtcWallet[80]{ "yourBtcAddress" };
|
||||
uint32_t PoolPort{ 21496 };
|
||||
uint32_t Timezone{ 2 };
|
||||
bool holdsData{ false };
|
||||
char WifiSSID[80]{ DEFAULT_SSID };
|
||||
char WifiPW[80]{ DEFAULT_WIFIPW };
|
||||
char PoolAddress[80]{ DEFAULT_POOLURL };
|
||||
char BtcWallet[80]{ DEFAULT_WALLETID };
|
||||
uint32_t PoolPort{ DEFAULT_POOLPORT };
|
||||
uint32_t Timezone{ DEFAULT_TIMEZONE };
|
||||
|
||||
void print()
|
||||
{
|
||||
Serial.print("WifiSSID:>");
|
||||
Serial.print(WifiSSID);
|
||||
Serial.print("<, ");
|
||||
Serial.print("WifiPW:>");
|
||||
Serial.print(WifiPW);
|
||||
Serial.print("<, ");
|
||||
Serial.print("PoolAddress:>");
|
||||
Serial.print(PoolAddress);
|
||||
Serial.print("<, ");
|
||||
Serial.print("BtcWallet:>");
|
||||
Serial.print(BtcWallet);
|
||||
Serial.print("<, ");
|
||||
Serial.print("PoolPort:>");
|
||||
Serial.print(PoolPort);
|
||||
Serial.print("<, ");
|
||||
Serial.print("Timezone:>");
|
||||
Serial.print(Timezone);
|
||||
Serial.println("<");
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _STORAGE_H_
|
@ -12,6 +12,7 @@
|
||||
#include "drivers/storage/SPIStorage.h"
|
||||
#include "drivers/storage/storage.h"
|
||||
|
||||
|
||||
// Flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
@ -53,7 +54,6 @@ void reset_configurations()
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
|
||||
void init_WifiManager()
|
||||
{
|
||||
#ifdef MONITOR_SPEED
|
||||
@ -76,7 +76,7 @@ void init_WifiManager()
|
||||
// Check if button2 is pressed to enter configMode with actual configuration
|
||||
if (!digitalRead(PIN_BUTTON_2)) {
|
||||
Serial.println(F("Button pressed to force start config mode"));
|
||||
resetConfig();
|
||||
reset_configurations();
|
||||
forceConfig = true;
|
||||
wm.setBreakAfterConfig(true); //Set to detect config edition and save
|
||||
}
|
||||
@ -84,16 +84,20 @@ void init_WifiManager()
|
||||
// Explicitly set WiFi mode
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
Settings = SPIFS.loadConfigFile();
|
||||
if (!Settings.holdsData)
|
||||
if (!SPIFS.loadConfigFile(&Settings))
|
||||
{
|
||||
Serial.println(F("No config file on internal flash."));
|
||||
SDCard sdc;
|
||||
if (sdc.loadConfigFile().holdsData)
|
||||
if (!sdc.loadConfigFile(&Settings))
|
||||
{
|
||||
Serial.println(F("No config file on internal flash, force config mode."));
|
||||
sdc.SD2SPIStorage(&SPIFS); // reboot on success.
|
||||
Serial.println(F("No config file on SD card."));
|
||||
forceConfig = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(F("Config file on SD card. Copy and restart."));
|
||||
sdc.SD2SPIStorage(&SPIFS); // reboot on success.
|
||||
}
|
||||
};
|
||||
|
||||
// Reset settings (only for development)
|
||||
@ -145,20 +149,22 @@ void init_WifiManager()
|
||||
|
||||
Serial.println("AllDone: ");
|
||||
if (forceConfig)
|
||||
// Run if we need a configuration
|
||||
{
|
||||
// Run if we need a configuration
|
||||
//No configuramos timeout al modulo
|
||||
wm.setConfigPortalBlocking(true); //Hacemos que el portal SI bloquee el firmware
|
||||
drawSetupScreen();
|
||||
Settings = TSettings();
|
||||
if (!wm.startConfigPortal(Settings.WifiSSID, Settings.WifiPW))
|
||||
|
||||
if (!wm.startConfigPortal(DEFAULT_SSID, DEFAULT_WIFIPW))
|
||||
{
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
//Could be break forced after edditing, so save new config
|
||||
|
||||
strncpy(Settings.PoolAddress, pool_text_box.getValue(), sizeof(Settings.PoolAddress));
|
||||
Settings.PoolPort = atoi(port_text_box_num.getValue());
|
||||
strncpy(Settings.BtcWallet, addr_text_box.getValue(), sizeof(Settings.BtcWallet));
|
||||
Settings.Timezone = atoi(time_text_box_num.getValue());
|
||||
|
||||
SPIFS.saveConfigFile(&Settings);
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
@ -212,6 +218,7 @@ void init_WifiManager()
|
||||
Settings.Timezone = atoi(time_text_box_num.getValue());
|
||||
Serial.print("TimeZone fromUTC: ");
|
||||
Serial.println(Settings.Timezone);
|
||||
|
||||
}
|
||||
|
||||
// Save the custom parameters to FS
|
||||
|
Loading…
Reference in New Issue
Block a user