## Менеджер файлов
При подключении по WS в локальной сети на платах esp8266/32 настроен веб сервер таким образом, что файлы из папки `/fs/` скачиваются по протоколу HTTP, что сильно быстрее остальных расположений. Путь этой папки можно настроить в конфиге библиотеки.

## OTA через curl
При настроенном HTTP сервере (для esp8266/32 всё работает по умолчанию) можно отправить файл для OTA обновления через командную строку:
- Для обычного режима и библиотек
  - `curl -vF flash=@file.bin http://x.x.x.x/ota` - обновление прошивки
  - `curl -vF fs=@file.bin http://x.x.x.x/ota` - обновление файловой системы
- Для ASYNC режима и библиотек
  - `curl -vF file=@file.bin http://x.x.x.x/ota?type=flash` - обновление прошивки
  - `curl -vF file=@file.bin http://x.x.x.x/ota?type=fs` - обновление файловой системы

Где file.bin - имя файла (любое)

## База проектов
В приложении есть вкладка Projects - список проектов на базе GyverHub. Как добавить свой проект в базу - читай [тут](https://github.com/GyverLibs/GyverHub-projects)

## OTA обновление с GitHub
GyverHub имеет встроенный механизм контроля версий и обновления прошивки с git: если приложение увидит новую версию прошивки для проекта, то предложит обновиться, обновление происходит "по воздуху" файлом из репозиторя. Для включения этого механизма в своём проекте нужно создать репозиторий проекта на GitHub и указать его в библиотеке.

### Репозиторий
Нужно зарегистрировать аккаунт на GitHub, создать репозиторий проекта и загрузить в него файлы.

### Прошивка
Загрузить скомпилированную прошивку (бинарник) в репозиторий по пути `/bin/firmware.bin`
- Можно загрузить gzip-сжатый вариант `/bin/firmware.bin.gz`, если в прошивке указан `ATOMIC_FS_UPDATE`. Приложение само определит, какой файл (.bin или .gz) нужно прошивать

### Файл информации 
Создать и заполнить файл информации о проекте в репозитории по пути `/project.json`. Образец:
```json
{
  "version": "1.0",
  "notes": "комментарии к версии обновления",
  "about": "Краткое описание проекта"
}
```

- Код версии будет проверяться приложением и сравниваться с текущей версией прошивки. Также код версии отображается в списке проектов в приложении, если проект добавлен в базу
- Комментарий к версии показывается в приложении в уведомлении о наличии более свежей версии проекта. Можно оставить пустую строку, если это не нужно. Для переноса строки используй `\n`, например `"notes": "строка 1\nстрока 2"`
- Краткое описание проекта отображается в списке проектов в приложении, если проект добавлен в базу. Если не добавлен - можно не указывать

### Указание версии
Чтобы приложение знало, что для устройства нужно искать обновление, нужно установить текущую версию проекта в прошивке при помощи функции `setVersion()` следующим образом: `hub.setVersion("аккаунт/репозиторий@версия")`, например `"AlexGyver/TachoLED@1.0"`

### Обновление
- Обновить код версии в `.setVersion()` в прошивке
- Обновить код версии в project.json
- Сбилдить и загрузить в репозиторий свежий бинарник

Платформа GyverHub предложит юзеру обновиться при открытии панели управления

### Другой путь к бинарнику
Можно указать свой путь к бинарному файлу, добавив в `project.json` поле `ota_url`:
```json
{
  "version": "1.0",
  "notes": "комментарии к версии обновления",
  "about": "Краткое описание проекта",
  "ota_url": "url файла"
}
```

## ПИН код
Устройству можно задать пин-код, который нужен будет ввести в приложении при открытии панели управления. `.setPIN(uint32_t pin)` - число должно быть больше `1000`, максимум 9 цифр, не должно начинаться с нуля `0`. 

> Примечание: *на сайте GyverHub* пин-код не является надёжной защитой и очень легко взламывается даже при небольших навыках программирования, так как система работает в браузере и все данные "торчат наружу". В нативном мобильном приложении взломать пароль будет уже гораздо сложнее.

## Свои поля в Info
Для добавления своих полей на страницу Info нужно подключить обрбаотчик `onInfo`, в котором определить тип блока и передать свои поля. Полный пример:
```cpp
hub.onInfo([](GHinfo_t info) {
  switch (info) {
    case GH_INFO_VERSION:
      hub.addInfo(F("Custom_ver"), F("v1.5"));
      break;

    case GH_INFO_NETWORK:
      hub.addInfo(F("Custom_net"), "net value");
      break;

    case GH_INFO_MEMORY:
      hub.addInfo(F("Custom_mem"), String(123));
      break;
      
    case GH_INFO_SYSTEM:
      hub.addInfo(F("Custom_sys"), "text");
      break;
  }
});
```

## Инструменты отладки
### События
Библиотека позволяет отлавливать системные события, для этого нужно подключить функцию вида `f(GHevent_t event, GHconn_t from)`, например:

```cpp
void setup() {
  hub.onEvent([](GHevent_t event, GHconn_t from){
    Serial.println(event);  // код события
    Serial.println(from);   // код подключения
  });
}
```

Для вывода текста вместо кодов (текст хранится в Flash памяти) можно использовать встроенные функции:
```cpp
Serial.print(GHreadConn(from));
Serial.print(": ");
Serial.println(GHreadEvent(event));
```

> Эти функции возвращают тип `const __FlashStringHelper*`

### Информация о билде
Можно получить полную информацию о текущем билде - зачем он вызван, кем, с какими параметрами и прочее. Для этого нужно в начале билда (перед компонентами) получить информацию в тип данных вида `GHbuild`:

```cpp
void build() {
  GHbuild b = hub.getBuild();
}
```

*Для чтения* доступны поля:
- `b.type` - тип билда, `GHbuild_t`
- `b.cmd` - команда, `GHevent_t`
- `b.client.id` - ID клиента, с которого пришел запрос
- `b.client.idString()` - ID клиента, с которого пришел запрос
- `b.client.from` - тип соединения с клиентом, `GHconn_t`

К `b.action` можно применять:
```cpp
const char* name;       // имя компонента
const char* nameStr();  // имя как const char*
String nameString();    // имя как String

const char* value;      // значение компонента
const char* valueStr(); // значение как const char*
String valueString();   // значение как String
int32_t valueInt();     // значение как int (32 бит)
float valueFloat();     // значение как float
bool valueBool();       // значение как bool
GHcolor valueColor();   // значение как GHcolor
GHflags valueFlags();   // значение как GHflags
GHpos valuePos();       // значение как GHpos
```

Можно использовать для отладки, для ручной работы с компонентами, а также для создания "белого списка" клиентов, которые имеют право на доступ к компонентам. Например:

```cpp
void build() {
  GHbuild b = hub.getBuild();

  // показать кнопку только для клиента с ID 12345
  if (b.client.idString() == "12345") hub.Button();

  Serial.println(b.action.valueFloat());  // получить значение
}
```

### Информация о действии
Внутри билдера можно вызвать `hub.action()` - функция вернёт текущее действие типа `GHaction`, из которого можно получить имя и значение компонента для ручной обработки как описано выше. Например:
```cpp
  Serial.println(hub.action().nameString());
  Serial.println(hub.action().valueFloat());
```

### Причина перезагрузки
Для определения причины перезагрузки можно подключить функцию вида `f(GHreason_t reason)`:

```cpp
void setup() {
  hub.onReboot([](GHreason_t reason){
    Serial.println(reason);  // код события
    // после выхода из этой функции МК будет перезагружен!
  });
}
```

- `GH_REB_BUTTON` - по кнопке с сайта
- `GH_REB_OTA` - после ОТА обновления
- `GH_REB_OTA_URL` - после OTA обновления по URL

> Для вывода события в виде текста можно использовать встроенную функцию: `Serial.println(GHreadReason(reason))`

### Наличие соединения
#### Со стороны приложения
При открытой панели управления приложение периодически посылает запросы на устройство. Если устройство не ответит - верхняя панель приложения загорится красным цветом - потеряно соединение с устройством. Пока вращается иконка обновления страницы - приложение ожидает ответа от устройства, таким образом можно контролировать качество соединения.

#### Со стороны устройства
Функция `.focused()` вернёт `true`, если клиент подключен к устройству по любому из способов связи. Для определения текущих подключений можно передать их в функцию, например `.focused(GH_MQTT)` вернёт `true`, если по MQTT есть подключение. Система поддерживает работу одновременно с несколькими клиентами по всем способам связи.

#### MQTT
Для определения наличия соединения с MQTT брокером можно опросить функцию `.online()` - вернёт `true` при наличии подключения к брокеру.

## Модули
Доступ к устройству из приложения можно контролировать при помощи системы модулей: их можно включать и отключать. По умолчанию все модули включены. Список модулей:

```cpp
GH_MOD_INFO     // разрешить вкладку инфо
GH_MOD_FSBR     // разрешить вкладку менеджера файлов
GH_MOD_FORMAT   // разрешить форматирование FS
GH_MOD_DOWNLOAD // разрешить скачивание
GH_MOD_UPLOAD   // разрешить загрузку
GH_MOD_OTA      // разрешить ОТА
GH_MOD_OTA_URL  // разрешить ОТА по URL
GH_MOD_REBOOT   // разрешить перезагрузку из инфо
GH_MOD_SET      // разрешить установку значений
GH_MOD_READ     // разрешить чтение
GH_MOD_DELETE   // разрешить удаление файлов
GH_MOD_RENAME   // разрешить переименование файлов

GH_MOD_SERIAL   // разрешить Serial
GH_MOD_BT       // разрешить Bluetooth
GH_MOD_WS       // разрешить WebSocket
GH_MOD_MQTT     // разрешить MQTT
```

Для включения нужно вызвать `.modules.set()`, для выключения - `.modules.unset()` и передать одну или несколько констант через `|`, например:

```cpp
// выключить менеджер файлов и кнопку переименования файлов
hub.modules.unset(GH_MOD_FSBR | GH_MOD_RENAME);

// разрешить связь по WS
hub.modules.set(GH_MOD_WS);
```

Визуально отображающиеся модули также скрываются из приложения (например кнопки или окно менеджера файлов). При изменении состояния типа подключения перезагружать хаб (stop-start) не нужно - отключение модуля типа связи работает на уровне парсера - устройство получит пакет, но проигнорирует его. Дополнительные функции:

```cpp
bool modules.read(GHmodule_t mod);  // проверить включен ли модуль
void modules.setAll();              // установить все
void modules.unsetAll();            // выключить все
```

## Обработка запроса
Библиотека позволяет вручную обрабатывать запрос с клиента: получить информацию о запросе и разрешить или запретить выполнение. Для этого нужно подключить обработчик:

#### Внешняя функция
```cpp
bool request(GHbuild b) {
  return true;
}
void setup() {
  hub.onRequest(request);
}
```

#### Лямбда
```cpp
hub.onRequest([](GHbuild b) -> bool {
  return true;
});
```

Если функция вернёт `true` - запрос будет выполнен. Если `false` - в приложении появится ошибка **Forbidden**. 

Данный механизм позволяет очень гибко настраивать взаимодействие устройства с клиентами, например разрешить выполнение некоторых команд только определённому клиенту (по его ID), или доступ к менеджеру файлов только по выбранным каналам связи. Пример вывода всей информации о запросе:

```cpp
hub.onRequest([](GHbuild b) -> bool {
  Serial.println("Request:");
  Serial.println(String("From: ") + GHreadConn(b.client.from));
  Serial.println(String("ID: ") + b.client.id);
  Serial.println(String("Source: ") + b.client.source);
  Serial.println(String("Act. name: ") + b.action.name);
  Serial.println(String("Act. value: ") + b.action.value);
  Serial.println(String("CMD: ") + GHreadEvent(b.cmd));
  return true;
});
```

Пример обработки:

```cpp
hub.onRequest([](GHbuild b) -> bool {
  // разрешить форматирование Flash только клиенту с ID 12345678
  if (b.cmd == GH_FORMAT && b.client.idString() != "12345678") return false;

  // разрешить установку значений на компонент my_switch только по вебсокет
  if (b.action.nameString() == "my_switch" && b.client.from != GH_WS) return false;

  // разрешить всё остальное
  return true;
});
```

## Обработка скачивания
Библиотека позволяет перехватывать запрос на скачивание файлов и менять источник данных. Для этого нужно подключить обработчик `onFetch(String& path, bool start)`. В нём параметр `path` является путём к файлу, по которому пришёл запрос, а `start` - индикатор начала скачивания. Обработчик вызывается два раза: в начале и в конце скачивания:

```cpp
hub.onFetch([](String& path, bool start) {
  Serial.println(path);
  Serial.println(start);
});
```

По умолчанию библиотека будет пытаться сама открыть файл по указанному пути и отправит его клиенту. Для смены источника есть две функции:
- `fetchFile(const char* path)` - сменить путь файла на указанный, он будет открыт и отправлен вместо указанного ранее
- `fetchBytes(uint8_t* bytes, uint32_t size)` - отправить бинарные данные, находящиеся по указателю `bytes` и имеющие размер `size`

### fetchBytes
Скачивание данных происходит *асинхронно*, поэтому данные, находящиеся по указателю, должны находиться в памяти между началом и окончанием загрузки: существовать глобально или быть выделенными динамически через `new/malloc/strdup`. Пример с отправкой кадра с камеры ESP32-CAM:

```cpp
camera_fb_t* frame;

void build() {
  // обязательно указать расширение картинки
  hub.Image_("cam", "frame.jpg");
}

hub.onFetch([](String& path, bool start) {
  if (path == "frame.jpg") {        // такой же путь
    if (start) {
      frame = esp_camera_fb_get();  // получить кадр
      if (frame) hub.fetchBytes((uint8_t*)frame->buf, frame->len);  // начать отправку
    } else {
      esp_camera_fb_return(frame);  // освободить кадр
      
      // можно инициировать загрузку следующего кадра, чтобы получилось "видео",
      // отправив пустое обновление на компонент Image
      //hub.sendUpdate("cam", "");
    }
  }
});
```

Полный пример см. в папке *examples*