## Основные понятия
- **Приложение**, **сайт GyverHub** - тут всё понятно
- **Устройство** - микроконтроллер с библиотекой GyverHub
- **Список устройств** - главный экран приложения
- **Панель управления, ПУ** - графический интерфейс, экран с элементами управления в приложении, который открывается при выборе устройства из списка устройств 
- **Клиент** - приложение/браузер, на котором открыта панель управления, т.е. приложение подключено и отправляет запросы на устройство
- **ID клиента** - уникальное имя клиента (браузера, приложения), генерируется при первом запуске приложения, можно изменить в настройках. *Только английские буквы, цифры и знак подчёркивания, максимум 8 символов*
- **Префикс** - уникальное имя сети устройств, должно быть одинаковым у всех устройств и у приложения, чтобы они могли общаться друг с другом: *только английские буквы, цифры и знак подчёркивания*
- **Экран информации** - экран, который открывается при открытии вкладки *Info* в выпадающем меню панели управления
- **Файловой менеджер** - экран, который открывается при открытии вкладки *File & OTA* в выпадающем меню панели управления
- **Компонент** - элемент панели управления
- **Имя компонента** - уникальное в пределах панели управления имя компонента: *только английские буквы, цифры и знак подчёркивания. Не должно начинаться с подчёркивания*

## Билдер
### Основная информация
**Билдер** - функция в программе для микроконтроллера, внутри которой собираются компоненты панели управления. В данной библиотеке, в отличие от [GyverPortal](https://github.com/GyverLibs/GyverPortal), билдер и компоненты выполняют сразу несколько функций:
- В билдере "собирается" панель управления - какие компоненты, как расположены и как называются
- В билдере обрабатываются действия с приложения - функция активного компонента вернёт `true`, если пришел запрос на изменение данных
- К активному компоненту может подключаться переменная, которая будет автоматически изменяться библиотекой при изменении значения в приложении
- Чтение текущих значений из подключенных переменных для отправки в приложение или систему умного дома - библиотека сама вызывает билдер когда ей нужно

> Примечание: в билдере не должно быть задержек `delay()` и прочего блокирующего или долго выполняющегося кода! Билдер вызывается в обработчике ответа приложению, во время выполнения билдера приложение ждёт ответа

> Примечание: во время выполнения билдера собирается строка с ответом приложению, поэтому внутри билдера рекомендуется максимально ограничить действия со `String` строками и динамическим выделением памяти (`malloc()`, `new`), а также по возможности использовать `F("строки")` в качестве аргументов функций конструктора

```cpp
void build() {
  // в теле билдера код будет выполняться в любом случае
  foo();

  if (hub.Component()) {
    // внутри условия активного компонента код будет
    // выполняться только при взаимодействии с этим компонентом
  }
}
```

### Внешний билдер
Создать функцию и передать объекту библиотеки:
```cpp
GyverHub hub("prefix");

void build() {
  // ... 
}

void setup() {
  hub.onBuild(build);
}
```

### Лямбда билдер
```cpp
GyverHub hub("prefix");

void setup() {
  hub.onBuild([](){
    // ...
  });
}
```

### Лямбда билдер внутри класса
На случай, если нужно обернуть GyverHub в класс
```cpp
class MyClass {
public:
  MyClass() {
    hub.onBuild([this](){
      // ...
    });
  }

  GyverHub hub;
};
```

### Обновление страницы
При открытии панели управления приложение запрашивает актуальный набор компонентов и их значений с устройства. При нажатии на кнопку обновления в панели управления происходит то же самое. Также обновить панель управления можно из билдера, вызвав функцию `.refresh()`:
- Обновление срабатывает только при типе билда `GH_BUILD_ACTION`, то есть при действии в панели управления (клик по кнопке, изменение слайдера) - вызвать можно например по условию срабатывания функции компонента
- Вызов обновления даёт библиотеке команду на отправку пакета с компонентами *после выхода из билдера*. То есть вызывать `.refresh()` несколько раз за билдер - безопасно, пакет будет отправлен максимум один раз

```cpp
void build() {
  if (hub.Button()) hub.refresh();
}
```

## Компоненты
### Стиль компонентов
У всех компонентов есть два варианта отображения в приложении - вертикальным списком и в виде "виджетов". По умолчанию компоненты отображаются в виде списка. Режим виджетов включается в прошивке для устройства, в то же время в приложении есть возможность принудительно отключить отображение стиля виджетов (*Info/Break widgets*) и выводить списком.

#### Кнопки
Кнопки ведут себя иначе чем остальные компоненты: в режиме списка они группируются по горизонтали, образуя блок с возможностью прокрутки. При отправке любого другого компонента (в том числе пустышки - `Space()`) строка кнопок автоматически завершается.

#### Список
"Крупные" компоненты типа HTML, Display, Canvas, Joystick и некоторые другие будут отображаться в режиме списка на всю ширину колонки приложения.

#### Виджеты
Виджеты строятся очень просто - несколько компонентов в одной строке, слева направо, сверху вниз. Если виджет не помещается в строку - он будет перенесён на следующую. Чтобы начать отображение в виде виджетов, нужно вызвать `BeginWidgets()`. В качестве аргумента можно передать минимальную высоту, если это нужно - все виджеты в строке растянутся до этой высоты. 

Чтобы закончить строку виджетов - `EndWidgets()`. Также строку виджетов автоматически прерывает заголовок `Title()`, после него виджеты продолжат выводиться без дополнительного вызова `BeginWidgets()`. Если вся панель управления состоит из виджетов - вызывать `EndWidgets()` в конце не нужно.

Ширина виджетов задаётся при помощи `WidgetSize()` в процентах, т.е. `100` - максимум. После изменения ширины все последующие компоненты будут иметь такую ширину, пока не будет установлена другая. Примечание: `BeginWidgets()` устанавливает ширину на 100%, а `EndWidgets()` - на 0%. Пример:
```cpp
  hub.BeginWidgets();
  hub.WidgetSize(50);
  hub.Button();
  hub.Button();
  hub.WidgetSize(100);
  hub.Slider();
```

Панель управления будет иметь следующий вид:
<table align="center">
  <tr>
    <td align="center">Button</td>
    <td align="center">Button</td>
  </tr>
  <tr>
    <td colspan="2" align="center">Slider</td>
  </tr>
</table>

Компонент `Space()` создаёт "пустой" виджет установленной выше ширины, может использоваться в дизайнерских целях.

### Типы компонентов
Компоненты бывают двух типов:
  - **Активные** - функция вызова компонента возвращает `true`, если из приложения было изменено значение или осуществлено нажатие (для кнопок). Например: кнопка, слайдер, поле ввода
  - **Пассивные** - служат для отображения информации. Например: лейбл, "дисплей", светодиод

```cpp
void build() {
  if (hub.Button()) {
    Serial.println("Button click!");
  }
}
```

### Список аргументов
Функции вызова компонентов могут принимать разное количество аргументов, если указывать не все - остальные настройки будут установлены по умолчанию. В [документации](https://github.com/GyverLibs/GyverHub/wiki) приведены функции компонентов как они есть, значения по умолчанию указаны через знак `=`. Например кнопка
```cpp
bool Button(bool* var = 0, FSTR text = 0, uint32_t color = GH_DEFAULT, int size = 22);
```

В программе можно вызвать следующим образом
```cpp
hub.Button();                   // всё по умолчанию
hub.Button(0, F("My button"));  // переменная не подключена, но подпись задана

GHbutton btn;
hub.Button(&btn, 0, GH_RED);    // подключить переменную и установить цвет
```

И так далее.

### Имя компонента
Почти у всех компонентов есть уникальное имя, по которому библиотека может обратиться к компоненту. Имя может генерироваться автоматически или задаваться вручную. Для установки вручную у всех компонентов есть вариант функции, оканчивающийся на `_`, например `Button_("имя")`. Эта функция полностью повторяет функцию с автоматическим именем, просто в начале добавляется ввод имени. Например:
```cpp
bool Button(GHbutton* var = 0, FSTR text = 0, uint32_t color = GH_DEFAULT, int size = 22);
bool Button_(FSTR name, GHbutton* var = 0, FSTR text = 0, uint32_t color = GH_DEFAULT, int size = 22);
```
Задавать имя вручную нужно в случаях, когда на компонент будут отправляться обновления дальше в программе или он будет читаться/устанавливаться из системы умного дома, то есть это имя будет указано вручную ещё в других местах. Если задать имя пустой строкой - оно будет сгенерировано автоматически, как при вызове функции компонента без `_` на конце.

> Автоматически заданное имя имеет вид `_n<индекс>`, где индекс увеличивается на каждом новом компоненте в билдере. Таким образом имена принимают вид `_n1`, `_n2`, `_n3`... Такие имена тоже можно использовать для отправки обновлений и прочего взаимодействия по имени, но рекомендуется задавать свои уникальные имена, чтобы программа не перестала работать после добавления новых компонентов до вашего компонента

### Оптимизация строк
Разница между `FSTR` и `String` - почти у всех функций компонентов есть два варианта - с FSTR и String строками, вариант с FSTR более быстрый и эффективный с точки зрения памяти. При использовании FSTR-строк во всех аргументах компилятор выбирает FSTR-функцию. Пример:
```cpp
  hub.LED_("имя", 0, F("название"));     // будет использована "медленная" версия функции
  hub.LED_(F("имя"), 0, F("название"));  // будет использована "быстрая" версия функции
```

Для установки строковых значений "по умолчанию" нужно ориентироваться на версию функции, которая будет вызвана. `F-строки` можно заменить нулём `0` или `nullptr`, а `String` - пустой строкой любого вида:

```cpp
  hub.LED_(F("имя"), 0, "");       // будет использована "медленная" версия функции
  hub.LED_(F("имя"), 0, nullptr);  // будет использована "быстрая" версия функции
  hub.LED_(F("имя"), 0, 0);        // будет использована "быстрая" версия функции
```

### Подключение переменной
Переменные "подключаются" к активным компонентам по адресу - `&`. У некоторых компонентов явно указан тип переменной, например к `Button(GHbutton* var)` (из документации) можно подключить переменную только типа `GHbutton`:

```cpp
GHbutton btn;

void build() {
  hub.Button(&btn);
}
```

У некоторых компонентов тип подключаемой переменной указан как `void*` - в этом случае тип может быть любым из поддерживаемых (см. ниже), а дальше нужно указать сам тип:
```cpp
int16_t sld;
float spin;

void build() {
  hub.Slider(&sld, GH_INT16);
  hub.Spinner(&spin, GH_FLOAT);
}
```

Поддерживаемые типы:
- `GH_NULL` - переменная не подключена 
- `GH_STR` - String строка 
- `GH_CSTR` - массив char[] (cstring строка)
- `GH_BOOL` - bool (1 байт логическая)
- `GH_INT8` - int8_t (1 байт со знаком)
- `GH_UINT8` - uint8_t (1 байт без знака)
- `GH_INT16` - int16_t (2 байта со знаком)
- `GH_UINT16` - uint16_t (2 байта без знака)
- `GH_INT32` - int32_t (4 байта со знаком)
- `GH_UINT32` - uint32_t (4 байта без знака)
- `GH_FLOAT` - float (4 байта плавающее)
- `GH_DOUBLE` - float (4/8 байта плавающее)

> Примечание: не рекомендуется использовать типы по названиям, такие как `int`, `long`, так как на разных платформах они занимают разное количество байт и программа может работать некорректно. Следует указывать явный тип.

Для массивов не нужен символ `&` перед именем переменной, т.к. массив уже является указателем сам на себя:
```cpp
int16_t sld[2];

void build() {
  hub.Slider(sld[0], GH_INT16);
  hub.Slider(sld[1], GH_INT16);
}
```

### Не подключать переменную
Если не нужно автоматическое обновление переменной, но нужно передать в функцию компонента другие настройки - адрес переменной можно указать как `0`, а тип - `GH_NULL`:  
```cpp
hub.Input(0, GH_NULL, "Input");
hub.Button(0, "Button");
```

### Автоматическое изменение
Подключенная переменная будет автоматически получать новое значение при изменении в панели управления, при этом функция компонента вернёт `true`:
```cpp
if (hub.Slider(&sld, GH_INT16)) {
  // Здесь переменная sld уже имеет новое значение
  // попадаем сюда, если в приложении изменена позиция слайдера
}
```

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

```cpp
struct Data {
  bool sw;
  int16_t sld;
  char str[10];
};

Data data;

void build() {
  bool flag = 0;
  flag |= hub.Switch(&data.sw);
  flag |= hub.Spinner(&data.sld, GH_INT16);
  flag |= hub.Input(data.str, GH_CSTR);

  if (flag) {
    // Сохранить в память
  }
}
```

В этом примере `flag` получит значение true, если будет действие *хотя бы по одному компоненту*, который к нему приравнивается через `|=`.

### Динамические компоненты
Компоненты можно создавать в цикле, они точно так же будут автоматически получать имена, и здесь же можно опрашивать действия:
```cpp
for (int i = 0; i < 5; i++) {
  hub.Button();
}
for (int i = 0; i < 5; i++) {
  if (hub.Button()) Serial.println(i);
}
```

Имена также можно задавать вручную, используя сложение строк:
```cpp
for (int i = 0; i < 5; i++) {
  hub.Switch_(String(F("sw")) + i);
}
```

Для подключения переменных в таком случае разумно использовать массив:
```cpp
uint16_t sliders[5];

void build() {
  for (int i = 0; i < 5; i++) {
    hub.Slider(sliders[i], GH_UINT16);
  }
}
```

### Вкладки
Основная идея вкладок состоит в генерации компонентов по группам, отдельно для каждой вкладки:
- Сам компонент вкладок меняет переменную типа `uint8_t` согласно номеру вкладки, счёт начинается с `0`
- По изменению вкладки библиотека сама обновляет панель управления
- Далее по значению переменной вкладок можно выстроить группы компонентов, через `if` или `switch`:

```cpp
uint8_t tab;

void build() {
  hub.Tabs(&tab, "Tab 1,Tab2,Tab 3");
  
  switch (tab) {
    case 0:
      hub.Button();
      break;
      
    case 1:
      hub.Switch();
      break;
      
    case 2:
      hub.Slider();
      break;
  }
}
```

В этом случае будут отображаться только компоненты, соответствующие текущей вкладке. Это простой и понятный подход, но билдер не будет иметь доступа к значениям подключенных переменных (для отправки обновлений или в систему умного дома). В библиотеке предусмотрена проверка `.buildRead()`, которая возвращает `true`, если билдер вызван для чтения значения по имени компонента. Конструкцию для вкладок можно переписать с учётом этого:

```cpp
uint8_t tab;
uint8_t sld0, sld1, sld2;

void build() {
  if (hub.Tabs(&tab, "Tab 1,Tab2,Tab 3")) hub.refresh();
  
  if (tab == 0 || hub.buildRead()) {
      hub.Slider(&sld0, GH_UINT8);
  }
  if (tab == 1 || hub.buildRead()) {
      hub.Slider(&sld1, GH_UINT8);
  }
  if (tab == 2 || hub.buildRead()) {
      hub.Slider(&sld2, GH_UINT8);
  }
}
```
Таким образом в панель управления будут выводиться только соответствующие вкладке компоненты, но библиотека будет иметь доступ ко всем компонентам для чтения подключенных к ним переменных.

### Меню
В приложении на экране устройства есть выпадающее меню (по умолчанию там пункты инфо и файловый менеджер), в него можно добавить свои пункты. Работает это точно так же, как у вкладок, но переменная номера пункта хранится в классе `GyverHub`. Пример:

```cpp
void build() {
  // добавить меню и 
  if (hub.Menu(F("button,switch,slider"))) {
    Serial.println(hub.menu);   // вывести номер пункта при нажатии
  }

  // отрисовка нужных компонентов
  // согласно пункту меню
  switch (hub.menu) {
    case 0:
      hub.Button();
      break;
      
    case 1:
      hub.Switch();
      break;
      
    case 2:
      hub.Slider();
      break;
  }
}
```

> Компонент `Menu` можно вызывать в любом месте билдера, но только один раз

## Отправка обновлений
Библиотека позволяет отправлять в приложение новые значения компонентов, на случай если они меняются где-то в программе (показания датчиков, состояние тумблеров). Обновления отображаются сразу, без обновления страницы в приложении. Отправить обновление можно на любой компонент, за исключением кнопок, заголовка и вкладок.

> Для отправки обновлений рекомендуется вручную задавать имя компонента

### Вручную
Для отправки обновления нужно вызвать `.sendUpdate(NAME, VALUE)`, где `NAME` - это имя компонента, `VALUE` - значение. Отправлять данные можно из любого места в программе (из основного цикла программы или из билдера). Компонент с указанным именем должен существовать на странице панели управления. `VALUE` принимает тип `String`, таким образом для отправки численных значений нужно преобразовать их в строку: `.sendUpdate("name", String(value))`. Обновление будет отправлено только в том случае, если панель управления текущим устройством открыта в приложении, библиотека сама следит за этим и отправит обновление всем подключенным клиентам.

В этом примере лейбл обновляется по таймеру, а светодиод - по клику по кнопке в билдере:
```cpp
void build() {
  hub.BeginWidgets();
  hub.WidgetSize(50);
  hub.Label_(F("lbl"));

  hub.WidgetSize(25);
  if (hub.Button()) {   // клик по кнопке
    static bool led;
    led = !led;
    hub.sendUpdate("led", String(led)); // обновляем led
  }
  
  hub.LED_(F("led"));
}

void loop() {
  // вывод значения millis() в лейбл lbl раз в секунду
  static GHtimer tmr(1000);
  if (tmr) hub.sendUpdate("lbl", String(millis()));
}
```

### Автоматически
Вызвать `.sendUpdate(NAME)` - библиотека вызовет билдер, сама прочитает значение указанного компонента и отправит пакет в приложение. Имена компонентов можно передать списком через запятую без пробелов `.sendUpdate("cmp1,cmp2,slider")` - библиотека сформирует пакет обновлений и отправит его в приложение. Это сильно эффективнее, чем отправлять обновления поимённо.

> Обновления по имени не работают внутри функции билдера

## Отправка уведомлений
### Push
С устройства можно отправить пуш-уведомление в браузер, для этого нужно вызвать `.sendPush(текст)`. Для получения уведомлений нужно разрешить их в браузере.
- При включенном MQTT уведомление доходит при любом открытом экране приложения, в отличие от Update обновления
- Уведомления лучше всего доходят при подключении по MQTT: с небольшой задержкой при свёрнутом браузере, с задержкой в несколько секунд при заблокированном экране (для смартфонов)

### Alert
Можно отправить стандартное всплывающее окно с кнопкой ОК при помощи `.sendAlert(текст)`.

### Всплывающее
В приложении есть всплывающее цветное уведомление, его можно вызвать с устройства при помощи функций `.sendNotice(текст)` и `.sendNotice(текст, цвет)`.

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

### Отправка
На стороне устройства используется функция `.print(текст)` и `.print(текст, цвет)` для отправки текста в приложение. Функция проверяет, по каким способам связи есть подключенные клиенты, и отправляет ответ только им. 

```cpp
void loop() {
  hub.print(String(millis()));
  delay(2000);
}
```

### Приём
Для принятия текста нужно подключить функцию вида `f(String& text)`:

```cpp
void cli(String& text) {
   Serial.println(text);
   hub.print(text);
   // Отправить в serial и обратно в консоль 
}

void setup() {
  hub.onCLI(cli);
}
```

Лямбда-вариант
```cpp
void setup() {
  hub.onCLI([](){
    Serial.println(text);
    hub.print(text);
    // Отправить в serial и обратно в консоль 
  });
}
```