Добавить выпуск прошивок из Keil и CCS
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
templates/
|
||||
c/ библиотеки на C99: заголовок, реализация, README, где есть — порт и тесты
|
||||
python/ модули на чистом Python 3.9+, только stdlib
|
||||
tools/ общие инструменты сборки и выпуска прошивок
|
||||
```
|
||||
|
||||
Пошаговая раскладка нового проекта и выбор портов для STM32F103, STM32G431
|
||||
@@ -48,6 +49,12 @@ templates/
|
||||
| [`python/protocan`](python/protocan) | разбор ProtoCAN, транспортный кадр моста, кадр SETGUI, кодеки каталога | stdlib, Python 3.9+ |
|
||||
| [`python/protocan/trends.py`](python/protocan/trends.py) | общие настройки графиков, ограниченная история, ctypes-декодер GAS/raw CAN | stdlib, опционально SETProtocol DLL/SO |
|
||||
|
||||
### Инструменты
|
||||
|
||||
| Инструмент | Что делает |
|
||||
|---|---|
|
||||
| [`tools/firmware-publish`](tools/firmware-publish) | единый BAT и конфигурации для проверки и публикации `.hex` Keil / `.bin` CCS 12 в каталоге SETGUI |
|
||||
|
||||
Общие тренды для Android GUI и SETGUI: [формат JSON, C99-ядро и адаптеры](c/set-protocol/docs/GUI_TRENDS.md).
|
||||
Общие масштабирование и маркеры: [C99, JNI и Python/Qt](c/set-protocol/docs/GUI_PLOT.md).
|
||||
|
||||
|
||||
221
tools/firmware-publish/PUBLISH_FIRMWARE.bat
Normal file
221
tools/firmware-publish/PUBLISH_FIRMWARE.bat
Normal file
@@ -0,0 +1,221 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions DisableDelayedExpansion
|
||||
|
||||
set "FW_TOOL_DIR=%~dp0"
|
||||
set "FW_MODE=--preflight"
|
||||
set "FW_CONFIG="
|
||||
set "FW_ASSUME_YES=0"
|
||||
set "FW_ALLOW_DIRTY=0"
|
||||
|
||||
:parse_arguments
|
||||
if "%~1"=="" goto arguments_done
|
||||
if /i "%~1"=="--preflight" (
|
||||
set "FW_MODE=--preflight"
|
||||
shift
|
||||
goto parse_arguments
|
||||
)
|
||||
if /i "%~1"=="--publish" (
|
||||
set "FW_MODE=--publish"
|
||||
shift
|
||||
goto parse_arguments
|
||||
)
|
||||
if /i "%~1"=="--yes" (
|
||||
set "FW_ASSUME_YES=1"
|
||||
shift
|
||||
goto parse_arguments
|
||||
)
|
||||
if /i "%~1"=="--allow-dirty" (
|
||||
set "FW_ALLOW_DIRTY=1"
|
||||
shift
|
||||
goto parse_arguments
|
||||
)
|
||||
if /i "%~1"=="--config" (
|
||||
if "%~2"=="" goto missing_config_value
|
||||
set "FW_CONFIG=%~2"
|
||||
shift
|
||||
shift
|
||||
goto parse_arguments
|
||||
)
|
||||
if /i "%~1"=="--help" goto usage
|
||||
if /i "%~1"=="-h" goto usage
|
||||
echo ERROR: unknown argument: %~1
|
||||
goto usage_error
|
||||
|
||||
:missing_config_value
|
||||
echo ERROR: --config requires a path.
|
||||
goto usage_error
|
||||
|
||||
:arguments_done
|
||||
if not defined FW_CONFIG (
|
||||
if exist "%CD%\firmware-release.cmd" (
|
||||
set "FW_CONFIG=%CD%\firmware-release.cmd"
|
||||
) else (
|
||||
set "FW_CONFIG=%FW_TOOL_DIR%firmware-release.cmd"
|
||||
)
|
||||
)
|
||||
for %%I in ("%FW_CONFIG%") do set "FW_CONFIG=%%~fI"
|
||||
if not exist "%FW_CONFIG%" (
|
||||
echo ERROR: publication config not found:
|
||||
echo %FW_CONFIG%
|
||||
echo Copy one of the examples to the firmware project root as
|
||||
echo firmware-release.cmd, then edit its values.
|
||||
exit /b 2
|
||||
)
|
||||
|
||||
for %%I in ("%FW_CONFIG%") do set "FW_CONFIG_DIR=%%~dpI"
|
||||
call "%FW_CONFIG%"
|
||||
if errorlevel 1 (
|
||||
echo ERROR: publication config returned an error.
|
||||
exit /b 2
|
||||
)
|
||||
|
||||
if not defined FW_PRODUCT goto missing_FW_PRODUCT
|
||||
if not defined FW_VERSION goto missing_FW_VERSION
|
||||
if not defined FW_VERSION_CODE goto missing_FW_VERSION_CODE
|
||||
if not defined FW_TRANSPORTS goto missing_FW_TRANSPORTS
|
||||
if not defined FW_IMAGE goto missing_FW_IMAGE
|
||||
if not defined FW_PROJECT_ROOT set "FW_PROJECT_ROOT=%FW_CONFIG_DIR%"
|
||||
|
||||
pushd "%FW_CONFIG_DIR%" >nul
|
||||
if errorlevel 1 (
|
||||
echo ERROR: cannot enter config directory: %FW_CONFIG_DIR%
|
||||
exit /b 2
|
||||
)
|
||||
for %%I in ("%FW_PROJECT_ROOT%") do set "FW_PROJECT_ROOT_ABS=%%~fI"
|
||||
for %%I in ("%FW_IMAGE%") do set "FW_IMAGE_ABS=%%~fI"
|
||||
if defined FW_SETGUI_ROOT for %%I in ("%FW_SETGUI_ROOT%") do set "FW_SETGUI_ROOT_ABS=%%~fI"
|
||||
popd
|
||||
|
||||
if not defined FW_SETGUI_ROOT_ABS call :find_setgui
|
||||
if not defined FW_SETGUI_ROOT_ABS (
|
||||
echo ERROR: SETGUI was not found.
|
||||
echo Set FW_SETGUI_ROOT in %FW_CONFIG%
|
||||
exit /b 2
|
||||
)
|
||||
set "FW_SETGUI_PUBLISHER=%FW_SETGUI_ROOT_ABS%\PUBLISH_FIRMWARE.bat"
|
||||
if not exist "%FW_SETGUI_PUBLISHER%" (
|
||||
echo ERROR: SETGUI publisher not found:
|
||||
echo %FW_SETGUI_PUBLISHER%
|
||||
exit /b 2
|
||||
)
|
||||
if not exist "%FW_IMAGE_ABS%" (
|
||||
echo ERROR: firmware image not found:
|
||||
echo %FW_IMAGE_ABS%
|
||||
exit /b 2
|
||||
)
|
||||
|
||||
for %%I in ("%FW_IMAGE_ABS%") do set "FW_IMAGE_EXT=%%~xI"
|
||||
if /i not "%FW_IMAGE_EXT%"==".hex" if /i not "%FW_IMAGE_EXT%"==".bin" (
|
||||
echo ERROR: FW_IMAGE must point to a .hex or .bin file, not %FW_IMAGE_EXT%.
|
||||
echo Keil must create a HEX file; CCS must run C2000 Hex Utility.
|
||||
exit /b 2
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Firmware publication
|
||||
echo Mode: %FW_MODE%
|
||||
echo Project: %FW_PROJECT_ROOT_ABS%
|
||||
echo Image: %FW_IMAGE_ABS%
|
||||
echo Product: %FW_PRODUCT%
|
||||
echo Version: %FW_VERSION% ^(%FW_VERSION_CODE%^)
|
||||
echo Transports: %FW_TRANSPORTS%
|
||||
if defined FW_BASE_ADDRESS echo Base address: %FW_BASE_ADDRESS%
|
||||
echo.
|
||||
|
||||
if /i "%FW_MODE%"=="--publish" call :guard_worktree
|
||||
if errorlevel 1 exit /b %errorlevel%
|
||||
|
||||
if /i "%FW_MODE%"=="--publish" if "%FW_ASSUME_YES%"=="0" (
|
||||
echo This will upload the image and change the public firmware catalog.
|
||||
choice /C YN /N /M "Continue [Y/N]? "
|
||||
if errorlevel 2 (
|
||||
echo Publication cancelled.
|
||||
exit /b 2
|
||||
)
|
||||
)
|
||||
|
||||
for %%T in (%FW_TRANSPORTS:,= %) do (
|
||||
call :publish_transport "%%~T"
|
||||
if errorlevel 1 goto publication_failed
|
||||
)
|
||||
|
||||
if /i "%FW_MODE%"=="--publish" (
|
||||
echo.
|
||||
echo Publication completed and verified for all transports.
|
||||
) else (
|
||||
echo.
|
||||
echo Preflight passed. Run with --publish only when this build is a release.
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
:publication_failed
|
||||
echo.
|
||||
echo ERROR: publication step failed; remaining transports were not processed.
|
||||
exit /b 1
|
||||
|
||||
:publish_transport
|
||||
set "FW_TRANSPORT=%~1"
|
||||
if "%FW_TRANSPORT%"=="" exit /b 0
|
||||
echo ----- %FW_TRANSPORT% -----
|
||||
if defined FW_BASE_ADDRESS (
|
||||
call "%FW_SETGUI_PUBLISHER%" %FW_MODE% --file "%FW_IMAGE_ABS%" --product "%FW_PRODUCT%" --version "%FW_VERSION%" --version-code "%FW_VERSION_CODE%" --transport "%FW_TRANSPORT%" --base-address "%FW_BASE_ADDRESS%" --notes "%FW_NOTES%"
|
||||
) else (
|
||||
call "%FW_SETGUI_PUBLISHER%" %FW_MODE% --file "%FW_IMAGE_ABS%" --product "%FW_PRODUCT%" --version "%FW_VERSION%" --version-code "%FW_VERSION_CODE%" --transport "%FW_TRANSPORT%" --notes "%FW_NOTES%"
|
||||
)
|
||||
exit /b %errorlevel%
|
||||
|
||||
:guard_worktree
|
||||
where git >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: Git is unavailable; source cleanliness was not checked.
|
||||
exit /b 0
|
||||
)
|
||||
git -C "%FW_PROJECT_ROOT_ABS%" rev-parse --is-inside-work-tree >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: project is not a Git worktree; source cleanliness was not checked.
|
||||
exit /b 0
|
||||
)
|
||||
git -C "%FW_PROJECT_ROOT_ABS%" diff --quiet HEAD -- >nul 2>nul
|
||||
if not errorlevel 1 exit /b 0
|
||||
if "%FW_ALLOW_DIRTY%"=="1" (
|
||||
echo WARNING: publishing from a worktree with tracked changes.
|
||||
exit /b 0
|
||||
)
|
||||
echo ERROR: tracked project files differ from HEAD.
|
||||
echo Commit the release sources or repeat with --allow-dirty deliberately.
|
||||
exit /b 3
|
||||
|
||||
:find_setgui
|
||||
for %%I in ("%FW_CONFIG_DIR%..\SETGUI" "%FW_CONFIG_DIR%..\..\SETGUI" "%FW_CONFIG_DIR%..\..\..\SETGUI") do (
|
||||
if exist "%%~fI\PUBLISH_FIRMWARE.bat" if not defined FW_SETGUI_ROOT_ABS set "FW_SETGUI_ROOT_ABS=%%~fI"
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
:missing_FW_PRODUCT
|
||||
echo ERROR: FW_PRODUCT is not set in %FW_CONFIG%
|
||||
exit /b 2
|
||||
:missing_FW_VERSION
|
||||
echo ERROR: FW_VERSION is not set in %FW_CONFIG%
|
||||
exit /b 2
|
||||
:missing_FW_VERSION_CODE
|
||||
echo ERROR: FW_VERSION_CODE is not set in %FW_CONFIG%
|
||||
exit /b 2
|
||||
:missing_FW_TRANSPORTS
|
||||
echo ERROR: FW_TRANSPORTS is not set in %FW_CONFIG%
|
||||
exit /b 2
|
||||
:missing_FW_IMAGE
|
||||
echo ERROR: FW_IMAGE is not set in %FW_CONFIG%
|
||||
exit /b 2
|
||||
|
||||
:usage
|
||||
echo Usage:
|
||||
echo PUBLISH_FIRMWARE.bat [--config PATH] --preflight
|
||||
echo PUBLISH_FIRMWARE.bat [--config PATH] --publish [--yes] [--allow-dirty]
|
||||
echo.
|
||||
echo Default mode is --preflight. Default config is firmware-release.cmd
|
||||
echo in the current directory or next to this script.
|
||||
exit /b 0
|
||||
|
||||
:usage_error
|
||||
call :usage
|
||||
exit /b 2
|
||||
159
tools/firmware-publish/README.md
Normal file
159
tools/firmware-publish/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Публикация прошивки из Keil и CCS 12
|
||||
|
||||
Этот комплект связывает проект прошивки с каталогом SETGUI. IDE по-прежнему
|
||||
собирает штатный загрузочный файл, а `PUBLISH_FIRMWARE.bat`:
|
||||
|
||||
1. читает метаданные выпуска из `firmware-release.cmd`;
|
||||
2. проверяет наличие и формат `.hex` или `.bin`;
|
||||
3. запускает локальную проверку SETGUI (`--preflight`) либо публикацию
|
||||
(`--publish`);
|
||||
4. для каждого транспорта загружает образ в Gitea, скачивает его обратно,
|
||||
сверяет SHA-256 и только после этого обновляет `update.json`.
|
||||
|
||||
Пароль в проекте не хранится. Публикатор использует учётные данные Gitea,
|
||||
которые сохранены в SETGUI через окно «Версия и обновление».
|
||||
|
||||
## Один раз на рабочем компьютере
|
||||
|
||||
1. Соберите или запустите SETGUI и сохраните в нём логин и пароль (либо токен
|
||||
вместо пароля) Gitea.
|
||||
2. Убедитесь, что у SETGUI создано Python-окружение `.venv`.
|
||||
3. Подключите `templates` как сабмодуль проекта либо используйте уже общий
|
||||
checkout. Не делайте отдельные исправленные копии скрипта в каждом проекте.
|
||||
4. Скопируйте подходящий файл из [`examples`](examples) в корень проекта под
|
||||
именем `firmware-release.cmd` и исправьте значения.
|
||||
|
||||
`firmware-release.cmd` содержит только метаданные:
|
||||
|
||||
```bat
|
||||
set "FW_PROJECT_ROOT=%~dp0"
|
||||
set "FW_SETGUI_ROOT=%~dp0..\SETGUI"
|
||||
set "FW_PRODUCT=F103DS18"
|
||||
set "FW_VERSION=1.1.0"
|
||||
set "FW_VERSION_CODE=0x00010100"
|
||||
set "FW_TRANSPORTS=can rs485"
|
||||
set "FW_BASE_ADDRESS=0x08003000"
|
||||
set "FW_IMAGE=mdk\build\ds18b20_f103.hex"
|
||||
set "FW_NOTES=Краткое описание выпуска"
|
||||
```
|
||||
|
||||
Пути считаются относительно каталога `firmware-release.cmd`. Если SETGUI не
|
||||
лежит рядом с проектом, укажите абсолютный `FW_SETGUI_ROOT` или системную
|
||||
переменную с тем же именем.
|
||||
|
||||
### Обязательные поля
|
||||
|
||||
| Поле | Значение |
|
||||
|---|---|
|
||||
| `FW_PRODUCT` | Стабильный идентификатор изделия. Не меняйте регистр/написание между версиями. |
|
||||
| `FW_VERSION` | Читаемая версия SemVer, например `1.1.0`. |
|
||||
| `FW_VERSION_CODE` | Число для сравнения версий. Рекомендуется `(major << 16) + (minor << 8) + patch`: `1.1.0` = `0x00010100`. |
|
||||
| `FW_TRANSPORTS` | Один или несколько транспортов через пробел: `can`, `rs485`, `tms`. |
|
||||
| `FW_IMAGE` | Готовый файл `.hex` или `.bin`. `.axf` и `.out` публиковать нельзя. |
|
||||
|
||||
`FW_BASE_ADDRESS` нужен для обычного бинарного образа STM32. Для Intel HEX
|
||||
адрес уже записан в файле, но поле каталога всё равно лучше заполнить адресом
|
||||
приложения. Для загрузочной таблицы TMS SCI8 оставьте поле пустым.
|
||||
|
||||
## Keil MDK / Arm Compiler 6
|
||||
|
||||
1. В **Options for Target → Output** включите **Create HEX File**.
|
||||
2. Укажите в `FW_IMAGE` реальный выходной файл, например
|
||||
`mdk\build\ds18b20_f103.hex`.
|
||||
3. Запустите полную сборку и локальную проверку. В `KONOR_ds18b20`, где
|
||||
сабмодуль `templates` подключён как `lib`, команда выглядит так:
|
||||
|
||||
```bat
|
||||
call "lib\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --preflight
|
||||
```
|
||||
|
||||
В **Options for Target → User → After Build/Rebuild** можно добавить эту же
|
||||
команду с `--preflight`. Если Keil запускает её из каталога `mdk`, передайте
|
||||
явный путь к конфигурации, например `--config "..\firmware-release.cmd"`.
|
||||
|
||||
Не ставьте `--publish` в post-build: иначе обычная сборка станет внешней
|
||||
операцией и сможет перезаписать опубликованную версию.
|
||||
|
||||
## Code Composer Studio 12 / C2000
|
||||
|
||||
SETGUI не преобразует `.out`. Для активной конфигурации **Debug и/или Release**
|
||||
включите **C2000 Hex Utility** и сформируйте загрузочный `.bin`. Для SCI8 boot
|
||||
TMS320F2812 используются параметры проекта:
|
||||
|
||||
```text
|
||||
--binary
|
||||
--boot
|
||||
--sci8
|
||||
```
|
||||
|
||||
Выход удобно складывать в `bin\${BuildArtifactFileBaseName}.bin`. Затем укажите
|
||||
этот путь в `FW_IMAGE`, `tms` в `FW_TRANSPORTS`, а `FW_BASE_ADDRESS` оставьте
|
||||
пустым.
|
||||
|
||||
После Build выполните:
|
||||
|
||||
```bat
|
||||
call "..\newProject\templates\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --preflight
|
||||
```
|
||||
|
||||
Команда выше соответствует текущей раскладке `SETGIT\BALZAM_ALL` и
|
||||
`SETGIT\newProject\templates`. Если `templates` подключён в сам проект как
|
||||
`lib\templates`, используйте `lib\templates\tools\firmware-publish\...`.
|
||||
|
||||
При желании ту же команду можно добавить в **Project Properties → Build →
|
||||
Steps → Post-build steps**. В конфигурации CCS, где Hex Utility не включён,
|
||||
файл `.bin` не обновится — это особенно важно отдельно проверить для Release.
|
||||
|
||||
## Выпуск
|
||||
|
||||
Рабочая последовательность одинакова для обеих IDE:
|
||||
|
||||
```bat
|
||||
rem 1. Собрать Release в IDE.
|
||||
|
||||
rem 2. Проверить метаданные, размер, имя, SHA-256 и запись каталога без сети.
|
||||
call "lib\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --preflight
|
||||
|
||||
rem 3. Закоммитить исходники выпуска и опубликовать.
|
||||
call "lib\tools\firmware-publish\PUBLISH_FIRMWARE.bat" --config "firmware-release.cmd" --publish
|
||||
```
|
||||
|
||||
Перед публикацией скрипт показывает все параметры, проверяет tracked-файлы
|
||||
Git и просит подтверждение. `--allow-dirty` осознанно разрешает публикацию из
|
||||
изменённого worktree, а `--yes` отключает только интерактивное подтверждение
|
||||
для доверенного CI:
|
||||
|
||||
```bat
|
||||
call PUBLISH_FIRMWARE.bat --publish --yes --allow-dirty
|
||||
```
|
||||
|
||||
Повторная публикация той же комбинации `product + versionCode + transport`
|
||||
заменяет запись каталога. Новый `versionCode` добавляет новую версию. Если при
|
||||
нескольких транспортах сеть оборвалась посередине, исправьте причину и повторите
|
||||
ту же команду: уже опубликованные записи будут безопасно заменены теми же
|
||||
данными.
|
||||
|
||||
## Контроль после публикации
|
||||
|
||||
Успешное завершение означает, что образ:
|
||||
|
||||
- загружен как asset выпуска;
|
||||
- скачан обратно и совпал по SHA-256;
|
||||
- записан в `firmware.releases` файла `update.json`;
|
||||
- повторно прочитан и разобран тем же кодом, который использует SETGUI.
|
||||
|
||||
После этого откройте в SETGUI **Версия и обновление → База прошивок → Обновить
|
||||
каталог** и проверьте изделие, версию и транспорт. Для окончательной проверки
|
||||
выполните загрузку на тестовое устройство именно тем транспортом, который
|
||||
указан в записи.
|
||||
|
||||
## Частые ошибки
|
||||
|
||||
| Сообщение | Что проверить |
|
||||
|---|---|
|
||||
| `firmware image not found` | Сборка завершилась успешно, `FW_IMAGE` задан относительно конфигурации, нужная конфигурация IDE создаёт `.hex/.bin`. |
|
||||
| `SETGUI was not found` | Исправьте `FW_SETGUI_ROOT`. |
|
||||
| `Missing build environment: .venv` | Создайте окружение SETGUI и установите зависимости проекта. |
|
||||
| Ошибка авторизации Gitea | Заново сохраните логин и токен в SETGUI; не записывайте токен в `.cmd`. |
|
||||
| `tracked project files differ from HEAD` | Закоммитьте точные исходники выпуска либо осознанно добавьте `--allow-dirty`. |
|
||||
| Версия не видна в SETGUI | Нажмите «Обновить каталог» и проверьте точное значение `FW_PRODUCT` и поддерживаемый выбранным устройством транспорт. |
|
||||
15
tools/firmware-publish/examples/ccs12-firmware-release.cmd
Normal file
15
tools/firmware-publish/examples/ccs12-firmware-release.cmd
Normal file
@@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
rem Copy this file to the firmware repository root as firmware-release.cmd.
|
||||
|
||||
set "FW_PROJECT_ROOT=%~dp0"
|
||||
rem This relative path matches SETGIT/BALZAM_ALL + SETGIT/newProject/SETGUI.
|
||||
rem Change it if the repositories are stored differently.
|
||||
set "FW_SETGUI_ROOT=%~dp0..\newProject\SETGUI"
|
||||
|
||||
set "FW_PRODUCT=BALZAM-166"
|
||||
set "FW_VERSION=1.0.0"
|
||||
set "FW_VERSION_CODE=0x00010000"
|
||||
set "FW_TRANSPORTS=tms"
|
||||
set "FW_BASE_ADDRESS="
|
||||
set "FW_IMAGE=ccs12_8_proj\bin\balzam_166_ccs12_8.bin"
|
||||
set "FW_NOTES=TMS320F2812 SCI8 boot image"
|
||||
13
tools/firmware-publish/examples/keil-firmware-release.cmd
Normal file
13
tools/firmware-publish/examples/keil-firmware-release.cmd
Normal file
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
rem Copy this file to the firmware repository root as firmware-release.cmd.
|
||||
|
||||
set "FW_PROJECT_ROOT=%~dp0"
|
||||
set "FW_SETGUI_ROOT=%~dp0..\SETGUI"
|
||||
|
||||
set "FW_PRODUCT=F103DS18"
|
||||
set "FW_VERSION=1.1.0"
|
||||
set "FW_VERSION_CODE=0x00010100"
|
||||
set "FW_TRANSPORTS=can rs485"
|
||||
set "FW_BASE_ADDRESS=0x08003000"
|
||||
set "FW_IMAGE=mdk\build\ds18b20_f103.hex"
|
||||
set "FW_NOTES=DS18B20 firmware release"
|
||||
Reference in New Issue
Block a user