...
суббота, 11 февраля 2017 г.
Ещё один Brainfuck интерпретатор
Brainfuck — язык программирования, созданный с одной целью: написать для него интерпретатор. Их было написано так много, что даже не буду давать на них ссылки. В этой статье на пальцах объясняется простой, но эффективный способ его оптимизации.
Для оптимизации нужно выполнить 3 правила:
-
Обратные инструкции (+ и -, > и <) взаимоуничтожаются, когда идут одна за другой. Код >++>><<- превращается в >+. Это, скорее, защита от дурака, чем оптимизация, т.к. такой код бессмысленен.
-
Повторяющиеся инструкции заменяются на команды, в аргументах которых сказано сколько раз конкретную инструкцию выполнить. Например, код +++ заменяется на команду ADD с аргументом 3, а код <<< на MOVE:-3.
- Добавляется новая команда, у которой в bf нет соответствующий инструкции. Команда присвоения значения. Код [+] и [-] обнуляет текущую ячейку, а команда ADD за этим кодом присваивает текущей ячейке значение своего аргументы. Код --[-]+++ заменяется на команду ASSIGN:3.
Список команд с описанием
Каждая команда имеет свой аргумент (далее просто arg). Значение аргумента ограничено только у команды ADD, т.к. размер одной ячейки 256.
Имя команды | Инструкции | Описание |
---|---|---|
ADD | + и - | Изменяет значение текущей ячейки на arg |
MOVE | > и < | Изменяет индекс текущей ячейки на arg |
READ | , | Пропускает из потока ввода arg символов и читает следующий за ними символ |
. | Печатает символ соответствующий значению текущей ячейки arg раз | |
GOTO | [ и ] | Переходит к выполнению arg по счёту команды относительно текущей |
ASSIGN | [+] и [-] | Присваивает текущей ячейке arg |
Пример кода интерпретатора
Интерпретация разделена на 3 этапа. Чтение исходного кода, его преобразование в оптимизированные команды и выполнение этих команд. Это всё происходит в функциях main и parse.
Первый и последний этап происходят сразу в функции main. Её код с комментариями:
int main(int argc, char* argv[]){
/* имя файла с исходниками передаётся первым аргументом */
if(argc == 1){
fprintf(stderr, "%s: Wrong argument count\n", argv[0]);
return 1;
};
/* файл открывается для чтения */
FILE* f = fopen(argv[1], "r");
if(f == NULL){
fprintf(stderr, "%s: Can't open %s\n", argv[0], argv[1]);
return 2;
};
/* исходный код читается из файла */
int n = fread(str, sizeof(char), SIZE - 1, f);
if(n == 0){
fprintf(stderr, "%s: Can't read data from %s\n", argv[0], argv[1]);
return 3;
};
str[n] = '\0';
/* проверяется правильность расстановки скобок */
fclose(f);
if(brackets()){
fprintf(stderr, "%s: Wrong brackets sequence\n", argv[0]);
return 4;
};
/* парсинг исходного кода */
parse();
/* выполнение команд */
ptr = mem;
int (*exe[])(int) = {exe_a, exe_b, exe_c, exe_d, exe_e, exe_f};
struct code* p = src + 1;
for(; p->cmd; ++p){
p += (*exe[p->cmd - 1])(p->arg);
};
return 0;
};
Оптимизация с помощью преобразования инструкций bf в команды для интерпретатора происходит в функции parse. Её код с комментариями:
void parse(){
/* инициализируется массив команд */
struct code *p = src;
p->cmd = 0;
p->arg = 0;
/* указатель на текущую инструкцию */
char* ch = str;
/* указатель на вершину стека необходимый для обработки скобок */
top = stk;
/* массив из указателей на функции обрабатывающих 8 возможных команд и комментарии */
int (*prs[])(struct code*) = {prs_0, prs_1, prs_2, prs_3, prs_4, prs_5, prs_6, prs_7, nothing};
/* парсинг */
for(; *ch != '\0'; ++ch){
p += (*prs[find(*ch)])(p);
};
/* нуль-терминальная команда в конце массива */
(p + 1)->cmd = 0;
};
Проверка эффективности
Для сравнения взял интерпретатор из официального ubuntu репозитория и несколько тестов из этого репозитория. В таблице записано среднее время работы теста в секундах.
Имя теста | Официальный | Из статьи |
---|---|---|
Long | 34 | 20 |
Mandelbrot | 21 | 26 |
EasyOpt | 24 | 27 |
Counter | 34 | 37 |
На всех тестах, кроме первого, официальный интерпретатор работает примерно на 12 процентов быстрее интерпретатора из статьи. В первом тесте, в отличии от остальных, в большинстве циклов количество инструкций > не совпадает с количеством инструкций <. Это делает циклы несбалансированными и не поддающимися оптимизации (другому виду оптимизации, не описанному в статье). Похоже, официальный интерпретатор оптимизирует сбалансированные циклы и получает от этого 12-процентный прирост к скорости, в то время как интерпретатор из статьи этого не делает и побеждает только на первом тесте. Хотя это неплохой результат для такого простого алгоритма оптимизации.
Исходники на Github
PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx
Название PROTEQ досталось по наследству от предыдущего проекта для которого была высказана похожая идея восстановления данных после сбоя.
Итак исходные данные:
• аппаратура — модуль FMC106P, две ПЛИС Virtex 6 LX130T-2 и LX240T-1
• скорость передачи — 5 Гбит/с
• число линий — 8
• источник данных — АЦП, нет возможности приостановить передачу
• двунаправленный обмен данными
• требуется реализовать быстрое восстановление данных после сбоя
• кодировка — 64/67
Основная идея — реализовать постоянную повторную передачу данных до прихода подтверждения о принятом пакете. В этом состоит главная особенность протокола и именно это позволяет ускорить восстановление данных.
Рассмотрим реализацию одной линии:
На передатчике реализованы четыре буфера. Источник данных обнаруживает свободный буфер и записывает в него данные. Запись идёт в строгом порядке 0,1,2,3; Узел передачи также по кругу опрашивает флаг заполнения буфера и начинает передавать данные из заполненных буферов. Когда от приёмника приходит подтверждение приёма, то буфер помечается свободным. При большом потоке данных подтверждение успевает прийти до передачи всех четырёх буферов и передача идёт на максимальной скорости. При маленьком потоке узел передачи успеет отправить повторный пакет, но он будет отброшен на приёмной стороне.
Это решение сильно ускоряет восстановление данных. В традиционной схеме приёмник должен принять пакет, провести его анализ и сформировать запрос на повторную передачу. Это долго.
На приёмнике также реализованы четыре буфера. В заголовке пакета находится номер буфера и пакет сразу отправляется в буфер назначения. В таком решении есть очень большая опасность. Если будет испорчен номер буфера, то возможно будут испорчены два пакета — и текущий пакет и уже принятый пакет. Чтобы этого избежать в пакете используются две контрольные суммы — одна идёт сразу за заголовком, вторая — в конце пакета.
Для скорости 5 Гбит/с на узле GTX используется шина 32 разряда с частотой 156.25 МГц. Обмен между FIFO и внутренними буферами идёт на частоте 250 МГц. Это обеспечивает запас скорости для восстановления. Например если произошла ошибка при передачи в буфер 1, а передача в буфер 2 и 3 произошла без ошибки, то запись в выходное FIFO будет задержана до повторного прихода пакета в буфер 1. Но после в FIFO будут сразу записаны пакеты из буферов 2 и 3.
Протокол использует фиксированную длину пакета — 256 слов по 32 бита.
Существуют два типа пакета:
- Пакет данных
- Служебный пакет
Формат пакета данных:
- CMD1
- CRC1
- DATA — 256 слов
- CMD2
- CRC2
Формат служебного пакета:
- CMD1
- CRC1
- CMD2
- CRC2
Накладные расходы достаточно низкие — только четыре 32-х разрядных слова. Полная длина пакета с данными — 260 слов.
Служебные пакеты передаются в случае отсутствия данных.
Для увеличения скорости реализована передача по нескольким линиям. Есть узел который работает с числом линий от 1 до 8. Для каждой линии ширина шины данных 32 бита. При использовании 8 линий общая ширина шины данных — 256 бит.
Расчётная скорость обмена для восьми линий на частоте 5 ГГц:
5000000000 * 64/67 * 256/260 * 8 / 8 /1024 / 1024 = 4484,7 Мбайт/с
Именно эта скорость достигнута в результате экспериментов. Ошибки периодически возникают, но они исправляются протоколом. Быстрое восстановление позволяет использовать небольшой размер FIFO для подключения АЦП.
Интересно сравнить эффективность PROTEQ с PCI Express v2.0 реализованном на этой же ПЛИС. Оба протокола используют восемь линков на скорости 5 Гбит/с. Максимальная скорость обмена составляет:
5000000000/8/1024/1024*8 = 4768 Мбайт/с
Эффективность PROTEQ:
4484/4768 = 0.94; т.е. 94% от максимальной скорости линии.
PCI Express обеспечивает скорость 3200 Мбайт/с
3200/4768 = 0.67; т.е. 67% от максимальной скорости линии.
Конечно главная причина низкой эффективности PCI Express v2.0 это применение кодировки 8/10;
Интересно также сравнить занимаемые ресурсы ПЛИС для реализации протоколов. На рисунке представлены области которые занимает PCI Express и PROTEQ со сравнимой функциональностью. При этом следует учитывать, что PCI Express использует ещё HARD блок.
Основным компонентом является prq_transceiver_gtx_m1
component prq_transceiver_gtx_m1 is
generic(
is_simulation : in integer:=0; -- 1 - режим моделирования
LINES : in integer; -- число MGT линий
RECLK_EN : in integer:=0; -- 1 - приёмник работает на частоте recclk
-- 0 - приёмник работает на частоте txoutclk
USE_REFCLK_IN : boolean:=FALSE; -- FALSE - используются вход MGTCLK
-- TRUE - используется вход REFCLK_IN
is_tx_dpram_use : in integer:=1; -- 1 - использование памяти для передачи
is_rx_dpram_use : in integer:=1 -- 1 - использование памяти для приёма
);
port(
clk : in std_logic; -- тактовая частота приложения - 266 МГц
clk_tx_out : out std_logic; -- тактовая частота передатчика - 156.25 МГц
clk_rx_out : out std_logic; -- тактовая частота приёмника - 156.25 МГц
--- SYNC ---
reset : in std_logic; -- 0 - сброс
sync_done : out std_logic; -- 1 - завершена инициализация
tx_enable : in std_logic; -- 1 - разрешение передачи данных
rx_enable : in std_logic; -- 1 - разрешение приёма данных
rst_buf : in std_logic:='0'; -- 1 - сброс буферов данных
transmitter_dis : in std_logic:='0'; -- 1 - запрет работы передатчика
---- DATA ----
tx_ready : out std_logic; -- 1 - готовность к передаче одного буфера
rx_ready : out std_logic; -- 1 - один буфер готов к чтению
tx_data : in std_logic_vector( 31+(LINES-1)*32 downto 0 ); -- данные для передачи
tx_data_we : in std_logic; -- 1 - запись данных
tx_data_title : in std_logic_vector( 3 downto 0 ); -- заголовок пакета
tx_data_eof : in std_logic; -- 1 - конец фрейма
tx_user_flag : in std_logic_vector( 7+(LINES-1)*8 downto 0 ); -- локальные флаги
tx_inject_error : in std_logic_vector( LINES-1 downto 0 ):=(others=>'0'); -- 1 - добавить ошибку в передаваемый буфер
rx_data : out std_logic_vector( 31+(LINES-1)*32 downto 0 ); -- принятые данные
rx_data_rd : in std_logic; -- 1 - чтение данных
rx_data_title : out std_logic_vector( 3 downto 0 ); -- заголовок принятого пакета
rx_data_eof : in std_logic; -- 1 - конец фрейма
rx_user_flag : out std_logic_vector( 7+(LINES-1)*8 downto 0 ); -- удалённые флаги
rx_user_flag_we : out std_logic_vector( (LINES-1) downto 0 ); -- 1 - обновление удалённых флагов
rx_crc_error_adr: in std_logic_vector( 2 downto 0 ):="000"; -- адрес счётчика ошибок
rx_crc_error : out std_logic_vector( 7 downto 0 ); -- счётчик ошибок для выбранного канала
--- MGT ---
rxp : in std_logic_vector( LINES-1 downto 0 );
rxn : in std_logic_vector( LINES-1 downto 0 );
txp : out std_logic_vector( LINES-1 downto 0 );
txn : out std_logic_vector( LINES-1 downto 0 );
refclk_in : in std_logic:='0';
mgtclk_p : in std_logic;
mgtclk_n : in std_logic
);
end component;
Группа сигналов tx_* организуют канал передачи.
Алгоритм передачи пакта:
- Ждать появления tx_ready=1
- Записать пакет по шине tx_data с помощью строба tx_data_we=1
- Сформировать tx_data_eof=1 на один такт — пакет будет отправлен
Записать можно от 1 до 256 слов, но в любом случае будет отправлен пакет длиной 256 слов.
Аналогично, группа сигналов rx_* организует канал приёма.
Алгоритм приёма пакета:
- Ждать появления rx_ready=1
- Прочитать пакет по шине rx_data с помощью строба rx_data_rd
- Сформировать rx_data_eof=1
Также как и при записи допускается прочитать не весь пакет.
Вместе с пакетом передаётся четырёхбитный заголовок. При передаче вместе с пакетом будет передано значение на входе tx_data_title. При приёме вместе с готовностью tx_ready=1 появится значение на rx_data_title. Это позволяет иметь несколько источников и приёмников данных при одном канале передаче.
Дополнительно в канале происходит передача флагов. Данные на входе передатчика tx_user_flag передаются на выход приёмника rx_user_flag. Это может использоваться для передачи флагов состояния FIFO.
Компонент prq_transceiver_gtx_m1 может передавать данные по нескольким линиям. Количество линий настраивается через параметр LINES; От количества линий зависит ширина шин tx_data, rx_data.
Вход tx_inject_error позволяет добавить ошибку в процессе передачи. Это позволяет проверить механизм восстановления данных.
Следующим уровнем является компонент prq_connect_m1
component prq_connect_m1 is
generic(
is_simulation : in integer:=0; -- 1 - режим моделирования
RECLK_EN : in integer:=0; -- 1 - приёмник работает на частоте recclk
-- 0 - приёмник работает на частоте txoutclk
is_tx_dpram_use : in integer:=1; -- 1 - использование памяти для передачи
is_rx_dpram_use : in integer:=1; -- 1 - использование памяти для приёма
FIFO0_WITH : in integer:=1; -- ширина FIFO0: 1 - нет, 32,64,128,256 бит
FIFO0_PAF : in integer:=16; -- уровень срабатывания флага PAF
FIFO0_DEPTH : in integer:=1024; -- глубина FIFO0
FIFO1_WITH : in integer:=1; -- ширина FIFO1: 1 - нет, 32,64,128,256 бит
FIFO1_PAF : in integer:=16; -- уровень срабатывания флага PAF
FIFO1_DEPTH : in integer:=1024 -- глубина FIFO0
);
port(
--- MGT ---
rxp : in std_logic_vector( 7 downto 0 );
rxn : in std_logic_vector( 7 downto 0 );
txp : out std_logic_vector( 7 downto 0 );
txn : out std_logic_vector( 7 downto 0 );
mgtclk_p : in std_logic;
mgtclk_n : in std_logic;
---- Tranceiver ----
clk : in std_logic; -- тактовая частота приложения - 266 МГц
clk_tx_out : out std_logic; -- тактовая частота передатчика - 156.25 МГц
clk_rx_out : out std_logic; -- тактовая частота приёмника - 156.25 МГц
--- SYNC ---
reset : in std_logic; -- 0 - сброс
sync_done : out std_logic; -- 1 - завершена инициализация
tx_enable : in std_logic; -- 1 - разрешение передачи данных
rx_enable : in std_logic; -- 1 - разрешение приёма данных
---- FIFO0 ----
fi0_clk : in std_logic:='0'; -- тактовая частота записи в FIFO
fi0_data : in std_logic_vector( FIFO0_WITH-1 downto 0 ):=(others=>'0'); -- шина данных FIFO
fi0_data_en : in std_logic:='0'; -- 1 - запись в FIFO
fi0_paf : out std_logic; -- 1 - FIFO почти полное
fi0_id : in std_logic_vector( 3 downto 0 ):=(others=>'0'); -- идентификатор FIFO
fi0_rstp : in std_logic:='0'; -- 1 - сброс FIFO
fi0_enable : in std_logic:='0'; -- 1 - разрешение передачи
fi0_prs_en : in std_logic:='0'; -- 1 - включение генератора псевдослучайной последовательности
fi0_ovr : out std_logic; -- 1 - переполнение FIFO
fi0_rd_full_speed: in std_logic:='0'; -- 1 - формирование выходного потока на полной скорости
---- FIFO1 ----
fi1_clk : in std_logic:='0'; -- тактовая частота записи в FIFO
fi1_data : in std_logic_vector( FIFO1_WITH-1 downto 0 ):=(others=>'0'); -- шина данных FIFO
fi1_data_en : in std_logic:='0'; -- 1 - запись в FIFO
fi1_paf : out std_logic; -- 1 - FIFO почти полное
fi1_id : in std_logic_vector( 3 downto 0 ):=(others=>'0'); -- идентификатор FIFO
fi1_rstp : in std_logic:='0'; -- 1 - сброс FIFO
fi1_enable : in std_logic:='0'; -- 1 - разрешение передачи
fi1_prs_en : in std_logic:='0'; -- 1 - включение генератора псевдослучайной последовательности
fi1_ovr : out std_logic; -- 1 - переполнение FIFO
fi1_rd_full_speed: in std_logic:='0'; -- 1 - формирование выходного потока на полной скорости
tx_inject_error : in std_logic_vector( 7 downto 0 ):=(others=>'0'); -- 1 - добавить ошибку в передаваемый буфер
tx_user_flag : in std_logic_vector( 63 downto 0 ):=(others=>'0'); -- локальные флаги
---- Приём данных ----
fifo_data : out std_logic_vector( 255 downto 0 ); -- выход FIFO
fifo_we : out std_logic; -- 1 - запись данных
fifo_id : out std_logic_vector( 3 downto 0 ); -- идентификатор FIFO
fifo_rdy : in std_logic_vector( 15 downto 0 ); -- готовность к приёму данных
rx_crc_error_adr: in std_logic_vector( 2 downto 0 ):="000"; -- адрес счётчика ошибок
rx_crc_error : out std_logic_vector( 7 downto 0 ); -- счётчик ошибок для выбранного канала
rx_user_flag : out std_logic_vector( 63 downto 0 ); -- удалённые флаги
rx_user_flag_we : out std_logic_vector( 7 downto 0 ) -- 1 - обновление удалённых флагов
);
end component;
Он уже реализует механизм передачи потока данных через FIFO по восьми линиям.
Структурная схема:
В его состав входят два FIFO, prq_transceiver, автоматы приёма и передачи.
Ширина входной шины каждого FIFO настраивается через параметры FIFOx_WITH, также настраивается количество слов и уровень срабатывания флага почти полного FIFO. Запись в каждое FIFO производится на своей тактовой частоте. Каждое FIFO сопровождается своим идентификатором fi0_id, fi1_id; Это позволяет разделить поток данных при приёме. После FIFO установлен генератор псевдослучайной последовательности. На схеме он обозначен как PSD. Генератор реализует три режима:
- Пропустить поток данных без изменений
- Подставить тестовую последовательность при сохранении скорости потока данных
- Подставить тестовую последовательность и передавать данные на полной скорости
На этом генераторе основано тестирование в составе реальных проектов. Этот генератор не занимает много места в ПЛИС, он есть во всех проектах и позволяет в любой момент провести проверку канала обмена.
Компоненты prq_connect_m1 и prq_transceiver_gtx_m1 являются базовыми. Они были разработаны для ПЛИС Virtex 6; Впоследствии разработаны компоненты prq_transceiver_gtx_m4 и prq_transceiver_gtx_m6;
- prq_transceiver_gtx_m4 — буфер 0 выделен для передачи командн
- prq_transceiver_gtx_m6 — для ПЛИС Kintex 7
Проект полностью моделируется, реализован последовательный запуск тестов через tcl файл.
И здесь я хочу выразить благодарность Игорю Казинову. Он внёс большой вклад в организацию моделирования в этом проекте.
Общий результат моделирования выглядит так:
Global PROTEQ TC log:
tc_00_0 PASSED
tc_00_1 PASSED
tc_00_2 PASSED
tc_00_3 PASSED
tc_02_0 PASSED
tc_02_1 PASSED
tc_02_2 PASSED
tc_02_3 PASSED
tc_02_4 PASSED
tc_02_5 PASSED
tc_03_0 PASSED
tc_05_0 PASSED
tc_05_1 PASSED
Каждая строчка в файле это результат выполнения одного теста. tc — это Test Case — тестовый случай. Для примера приведу компонент tc_00_1 — проверка передачи пакета и внесение одиночной ошибки в процесс передачи.
-------------------------------------------------------------------------------
--
-- Title : tc_00_1
-- Author : Dmitry Smekhov
-- Company : Instrumental Systems
-- E-mail : dsmv@insys.ru
--
-- Version : 1.0
--
-------------------------------------------------------------------------------
--
-- Description : Проверка на тесте prq_transceiver_tb
--
-- Приём 32-х пакетов.
-- Одиночная ошибка
--
-------------------------------------------------------------------------------
--
-- Rev0.1 - debug test #1
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
library std;
use std.textio.all;
library work;
use work.prq_transceiver_tb_pkg.all; -- to bind testbench and its procedures
use work.utils_pkg.all; -- for "stop_simulation"
use work.pck_fio.all;
entity tc_00_1 is
end tc_00_1;
architecture bhv of tc_00_1 is
signal tx_err : std_logic;
signal rx_err : std_logic;
begin
----------------------------------------------------------------------------------
-- Instantiate TB
TB : prq_transceiver_tb
generic map(
max_pkg => 32, -- число пакетов, которое нужно принять
max_time => 100 us, -- максимальное время теста
tx_pause => 1 ns, -- минимальное время между пакетами при передаче
rx_pause => 1 ns -- минимальное время между пакетами при приёме
)
port map(
tx_err => tx_err, -- сигнал ошибки m1->m2
rx_err => rx_err -- сигнал ошибки m2->m1
);
----------------------------------------------------------------------------------
--
-- Define ERR at TIME#
--
tx_err <= '0', '1' after 27 us, '0' after 27.001 us;
rx_err <= '0';
----------------------------------------------------------------------------------
end bhv;
Компонент очень простой. Он вызывает prq_transceiver_tb (а вот он как раз сложный), настраивает параметры и формирует сигнал tx_err который вносит ошибку на линию передачи.
Остальные компоненты от tc_00_1 до tc_00_5 примерно такие же, они отличаются настроенными параметрами, что позволяет проверить передачу данных при разных условиях.
Компонент prq_transceiver_tb гораздо сложнее. Собственно он формирует тестовую последовательность, передаёт поток между двумя prq_transceiver_gtx_m1 и проверяет принятый поток данных. При необходимости — вносит ошибки в процесс передачи.
Сам компонент здесь:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use work.prq_transceiver_gtx_m1_pkg.all;
library std;
use std.textio.all;
use work.pck_fio.all;
use work.utils_pkg.all;
entity prq_transceiver_tb is
generic(
max_pkg : integer:=0; -- число пакетов, которое нужно принять
max_time : time:=100 us; -- максимальное время теста
tx_pause : time:=100 ns; -- минимальное время между пакетами при передаче
rx_pause : time:=100 ns -- минимальное время между пакетами при приёме
);
port(
tx_err : in std_logic:='0'; -- сигнал ошибки m1->m2
rx_err : in std_logic:='0' -- сигнал ошибки m2->m1
);
end prq_transceiver_tb;
architecture TB_ARCHITECTURE of prq_transceiver_tb is
signal clk : std_logic:='0';
signal reset : STD_LOGIC;
signal tx_data : STD_LOGIC_VECTOR(31 downto 0);
signal tx_data_we : STD_LOGIC;
signal tx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal rx_data_rd : STD_LOGIC;
signal rxp : STD_LOGIC_VECTOR(0 downto 0);
signal rxn : STD_LOGIC_VECTOR(0 downto 0);
signal mgtclk_p : STD_LOGIC;
signal mgtclk_n : STD_LOGIC;
signal mgtclk : std_logic:='0';
-- Observed signals - signals mapped to the output ports of tested entity
signal clk_tx_out : STD_LOGIC;
signal clk_rx_out : STD_LOGIC;
signal sync_done : STD_LOGIC;
signal tx_enable : STD_LOGIC;
signal rx_enable : STD_LOGIC;
signal tx_ready : STD_LOGIC;
signal rx_ready : STD_LOGIC;
signal rx_data : STD_LOGIC_VECTOR(31 downto 0);
signal rx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal txp : STD_LOGIC_VECTOR(0 downto 0);
signal txn : STD_LOGIC_VECTOR(0 downto 0);
signal m2_txp : STD_LOGIC_VECTOR(0 downto 0);
signal m2_txn : STD_LOGIC_VECTOR(0 downto 0);
signal m2_rxp : STD_LOGIC_VECTOR(0 downto 0);
signal m2_rxn : STD_LOGIC_VECTOR(0 downto 0);
signal tx_err_i : std_logic_vector( 0 downto 0 );
signal rx_err_i : std_logic_vector( 0 downto 0 );
signal tx_data_eof : std_logic;
signal rx_data_eof : std_logic;
signal m2_clk : std_logic:='0';
signal m2_tx_data : STD_LOGIC_VECTOR(31 downto 0);
signal m2_tx_data_we : STD_LOGIC;
signal m2_tx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal m2_rx_data_rd : STD_LOGIC;
signal m2_tx_data_eof : std_logic;
signal m2_rx_data_eof : std_logic;
-- Observed signals - signals mapped to the output ports of tested entity
signal m2_clk_tx_out : STD_LOGIC;
signal m2_clk_rx_out : STD_LOGIC;
signal m2_sync_done : STD_LOGIC;
signal m2_tx_enable : STD_LOGIC;
signal m2_rx_enable : STD_LOGIC;
signal m2_tx_ready : STD_LOGIC;
signal m2_rx_ready : STD_LOGIC;
signal m2_rx_data : STD_LOGIC_VECTOR(31 downto 0);
signal m2_rx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal tx_user_flag : std_logic_vector( 7 downto 0 ):=x"A0";
signal rx_user_flag : std_logic_vector( 7 downto 0 );
signal rx_user_flag_we : std_logic_vector( 0 downto 0 );
signal m2_tx_user_flag : std_logic_vector( 7 downto 0 ):=x"C0";
signal m2_rx_user_flag : std_logic_vector( 7 downto 0 );
signal m2_rx_user_flag_we : std_logic_vector( 0 downto 0 );
signal m2_reset : std_logic;
begin
clk <= not clk after 1.9 ns;
mgtclk <= not mgtclk after 3.2 ns;
m2_clk <= not m2_clk after 1.8 ns;
mgtclk_p <= mgtclk;
mgtclk_n <= not mgtclk;
-- Unit Under Test port map
UUT : prq_transceiver_gtx_m1
generic map (
is_simulation => 1, -- 1 - режим моделирования
LINES => 1,
is_tx_dpram_use => 1, -- 1 - использование памяти для передачи
is_rx_dpram_use => 0 -- 1 - использование памяти для приёма
)
port map (
clk => clk,
clk_tx_out => clk_tx_out,
clk_rx_out => clk_rx_out,
--- SYNC --- --- SYNC ---
reset => reset,
sync_done => sync_done,
tx_enable => tx_enable,
rx_enable => rx_enable,
---- DATA ---- ---- DATA ----
tx_ready => tx_ready,
rx_ready => rx_ready,
tx_data => tx_data,
tx_data_we => tx_data_we,
tx_data_title => tx_data_title,
tx_data_eof => tx_data_eof,
tx_user_flag => tx_user_flag,
rx_data => rx_data,
rx_data_rd => rx_data_rd,
rx_data_title => rx_data_title,
rx_data_eof => rx_data_eof,
rx_user_flag => rx_user_flag,
rx_user_flag_we => rx_user_flag_we,
--- MGT --- --- MGT ---
rxp => rxp,
rxn => rxn,
txp => txp,
txn => txn,
mgtclk_p => mgtclk_p,
mgtclk_n => mgtclk_n
);
UUT2 : prq_transceiver_gtx_m1
generic map (
is_simulation => 1, -- 1 - режим моделирования
LINES => 1,
is_tx_dpram_use => 0, -- 1 - использование памяти для передачи
is_rx_dpram_use => 1 -- 1 - использование памяти для приёма
)
port map (
clk => m2_clk,
clk_tx_out => m2_clk_tx_out,
clk_rx_out => m2_clk_rx_out,
--- SYNC --- --- SYNC ---
reset => m2_reset,
sync_done => m2_sync_done,
tx_enable => m2_tx_enable,
rx_enable => m2_rx_enable,
---- DATA ---- ---- DATA ----
tx_ready => m2_tx_ready,
rx_ready => m2_rx_ready,
tx_data => m2_tx_data,
tx_data_we => m2_tx_data_we,
tx_data_title => m2_tx_data_title,
tx_data_eof => m2_tx_data_eof,
tx_user_flag => m2_tx_user_flag,
rx_data => m2_rx_data,
rx_data_rd => m2_rx_data_rd,
rx_data_title => m2_rx_data_title,
rx_data_eof => m2_rx_data_eof,
rx_user_flag => m2_rx_user_flag,
rx_user_flag_we => m2_rx_user_flag_we,
--- MGT --- --- MGT ---
rxp => m2_rxp,
rxn => m2_rxn,
txp => m2_txp,
txn => m2_txn,
mgtclk_p => mgtclk_p,
mgtclk_n => mgtclk_n
);
rx_err_i <= (others=>rx_err);
tx_err_i <= (others=>tx_err);
rxp <= m2_txp or rx_err_i;
rxn <= m2_txn or rx_err_i;
m2_rxp <= txp or tx_err_i;
m2_rxn <= txn or tx_err_i;
reset <= '0', '1' after 1 us;
m2_reset <= '0', '1' after 2 us;
tx_enable <= '0', '1' after 22 us;
rx_enable <= '0', '1' after 22 us;
m2_tx_enable <= '0', '1' after 24 us;
m2_rx_enable <= '0', '1' after 24 us;
m2_tx_data_we <= '0';
m2_tx_data <= (others=>'0');
m2_tx_data_title <= "0000";
m2_tx_data_eof <= '0';
pr_tx_data: process begin
tx_data_we <= '0';
tx_data_eof <= '0';
tx_data <= x"AB000000";
tx_data_title <= "0010";
loop
wait until rising_edge( clk ) and tx_ready='1';
tx_data_we <= '1' after 1 ns;
for ii in 0 to 255 loop
wait until rising_edge( clk );
tx_data <= tx_data + 1 after 1 ns;
end loop;
tx_data_we <= '0' after 1 ns;
wait until rising_edge( clk );
tx_data_eof <= '1' after 1 ns;
wait until rising_edge( clk );
tx_data_eof <= '0' after 1 ns;
wait until rising_edge( clk );
wait for tx_pause;
end loop;
end process;
pr_rx_data: process
variable expect_data : std_logic_vector( 31 downto 0 ):= x"AB000000";
variable error_cnt : integer:=0;
variable pkg_cnt : integer:=0;
variable pkg_ok : integer:=0;
variable pkg_error : integer:=0;
variable index : integer;
variable flag_error : integer;
variable tm_start : time;
variable tm_stop : time;
variable byte_send : real;
variable tm : real;
variable velocity : real;
variable tm_pkg : time:=0 ns;
variable tm_pkg_delta : time:=0 ns;
variable L : line;
begin
m2_rx_data_rd <= '0';
m2_rx_data_eof <= '0';
--fprint( output, L, "Чтение данных\n" );
loop
loop
wait until rising_edge( m2_clk );
if( m2_rx_ready='1' or now>max_time ) then
exit;
end if;
end loop;
if( now>max_time ) then
exit;
end if;
if( pkg_cnt=0 ) then
tm_start:=now;
tm_pkg:=now;
end if;
tm_pkg_delta := now - tm_pkg;
fprint( output, L, "PKG=%3d %10r ns %10r ns\n", fo(pkg_cnt), fo(now), fo(tm_pkg_delta) );
tm_pkg:=now;
index:=0;
flag_error:=0;
m2_rx_data_rd <= '1' after 1 ns;
wait until rising_edge( m2_clk );
loop
wait until rising_edge( m2_clk );
if( expect_data /= m2_rx_data ) then
if( error_cnt<32 ) then
fprint( output, L, "ERROR: pkg=%d index=%d expect=%r read=%r \n",
fo(pkg_cnt), fo(index), fo(expect_data), fo(m2_rx_data) );
end if;
error_cnt:=error_cnt+1;
flag_error:=1;
end if;
index:=index+1;
expect_data:=expect_data+1;
if( index=255 ) then
m2_rx_data_rd <= '0' after 1 ns;
end if;
if( index=256 ) then
exit;
end if;
end loop;
if( flag_error=0 ) then
pkg_ok:=pkg_ok+1;
else
pkg_error:=pkg_error+1;
end if;
wait until rising_edge( m2_clk );
m2_rx_data_eof <= '1' after 1 ns;
wait until rising_edge( m2_clk );
m2_rx_data_eof <= '0' after 1 ns;
wait until rising_edge( m2_clk );
--wait until rising_edge( m2_clk );
wait for rx_pause;
pkg_cnt:=pkg_cnt+1;
if( pkg_cnt=max_pkg ) then
exit;
end if;
end loop;
tm_stop:=now;
fprint( output, L, "Завершён приём данных: %r ns\n", fo(now) );
fprint( output, L, " Принято пакетов: %d\n", fo( pkg_cnt ) );
fprint( output, L, " Правильных: %d\n", fo( pkg_ok ) );
fprint( output, L, " Ошибочных: %d\n", fo( pkg_error ) );
fprint( output, L, " Общее число ошибок: %d\n", fo( error_cnt ) );
byte_send:=real(pkg_cnt)*256.0*4.0;
tm := real(tm_stop/ 1 ns )-real(tm_start/ 1 ns);
velocity := byte_send*1000000000.0/(tm*1024.0*1024.0);
fprint( output, L, " Скорость передачи: %r МБайт/с\n", fo( integer(velocity) ) );
flag_error:=0;
if( max_pkg>0 and pkg_cnt/=max_pkg ) then
flag_error:=1;
end if;
if( flag_error=0 and pkg_cnt>0 and error_cnt=0 ) then
--fprint( output, L, "\n\nТест завершён успешно\n\n" );
fprint( output, L, "\n\nTEST finished successfully\n\n" );
else
--fprint( output, L, "\n\nТест завершён с ошибками\n\n" );
fprint( output, L, "\n\nTEST finished with ERR\n\n" );
end if;
utils_stop_simulation;
wait;
end process;
pr_tx_user_flag: process begin
tx_user_flag <= tx_user_flag + 1;
wait for 1 us;
end process;
pr_m2_tx_user_flag: process begin
m2_tx_user_flag <= m2_tx_user_flag + 1;
wait for 1.5 us;
end process;
end TB_ARCHITECTURE;
Результат работы теста:
# KERNEL: PKG= 0 25068 ns 0 ns
# KERNEL: PKG= 1 26814 ns 1746 ns
# KERNEL: PKG= 2 35526 ns 8712 ns
# KERNEL: PKG= 3 36480 ns 954 ns
# KERNEL: PKG= 4 37434 ns 954 ns
# KERNEL: PKG= 5 38388 ns 954 ns
# KERNEL: PKG= 6 39342 ns 954 ns
# KERNEL: PKG= 7 40858 ns 1515 ns
# KERNEL: PKG= 8 42597 ns 1738 ns
# KERNEL: PKG= 9 44339 ns 1742 ns
# KERNEL: PKG= 10 46081 ns 1742 ns
# KERNEL: PKG= 11 47827 ns 1746 ns
# KERNEL: PKG= 12 49566 ns 1738 ns
# KERNEL: PKG= 13 51309 ns 1742 ns
# KERNEL: PKG= 14 53051 ns 1742 ns
# KERNEL: PKG= 15 54790 ns 1738 ns
# KERNEL: PKG= 16 56536 ns 1746 ns
# KERNEL: PKG= 17 58278 ns 1742 ns
# KERNEL: PKG= 18 60021 ns 1742 ns
# KERNEL: PKG= 19 61759 ns 1738 ns
# KERNEL: PKG= 20 63502 ns 1742 ns
# KERNEL: PKG= 21 65248 ns 1746 ns
# KERNEL: PKG= 22 66990 ns 1742 ns
# KERNEL: PKG= 23 68729 ns 1738 ns
# KERNEL: PKG= 24 70471 ns 1742 ns
# KERNEL: PKG= 25 72210 ns 1738 ns
# KERNEL: PKG= 26 73953 ns 1742 ns
# KERNEL: PKG= 27 75699 ns 1746 ns
# KERNEL: PKG= 28 77441 ns 1742 ns
# KERNEL: PKG= 29 79180 ns 1738 ns
# KERNEL: PKG= 30 80922 ns 1742 ns
# KERNEL: PKG= 31 82661 ns 1738 ns
# KERNEL: Завершён приём данных: 83598 ns
# KERNEL: Принято пакетов: 32
# KERNEL: Правильных: 32
# KERNEL: Ошибочных: 0
# KERNEL: Общее число ошибок: 0
# KERNEL: Скорость передачи: 534 МБайт/с
# KERNEL:
# KERNEL:
# KERNEL: TEST finished successfully
Обратите внимание на правую колонку. Это время между принятыми пакетами. Обычное время ~1740 ns, однако пакет 2 принят с задержкой в 8712 ns. Это как раз действовал сигнал ошибки. И ещё обратите внимание что следующие пакеты приняты с задержкой 954 ns. Это из-за того, что неправильно был принят только один пакет, а остальные дожидались своей очереди в буферной памяти.
Хочу отметить, что автоматизированный запуск тестов мне очень помог при отладке протокола. Это позволило держать все изменения под контролем. И небольшие изменения в исходном коде не смогли привести к обрушению проекта.
Проект PROTEQ доступен как OpenSource. Ссылка на сайт — в моём профиле.
Комментарии (0)
Корпорации и стартапы: модели успешного сотрудничества
Юрий Васильев, директор «Русское техническое общество»
«Корпорация – это путевка для «пилотирования»
Если стартап столкнулся с рынком через корпорацию, то самое главное для него — это возможность получить пилотную площадку с обратной связью от рынка.
Например, к нам обратился проект по аэромониторингу строительных площадок. Ни мы, ни они до конца на тот момент не понимали, какой будет конечная технология. Чтобы предоставить им тестовую площадку (конкретный микрорайон), мне пришлось пройти пять ступеней согласований, вплоть до охранников территории. Надо помнить, что как только ты спускаешься вниз, то никому там уже ничего не нужно. С инновационными технологиями идет вмешательство в спокойную жизнь большой корпорации. И в этом смысле корпорация, с которой вы работаете, является вашей путевкой для «пилотирования». На входе необходимо отработать свой полуфабрикат с реальными экспертами, с рынком, без каких-либо обещаний.
За три месяца работы с проектом мы предоставили экономию в 22 млн рублей. Сейчас уже создано СП с прицелом на ближневосточный регион.
Вартан Минасян, руководитель направления по инвестициям и инновациям «Лаборатории Касперского»
«Мы редко общаемся со стартапами, которые целятся только в одну страну»
«Лаборатория Касперского», как один из мировых экспертов в области информационной безопасности, заинтересована в том, чтобы на рынке появлялись новые продукты в этой сфере. Стартапам, со своей стороны, важно понимать, что за продукт надо создать, как его правильно сделать, как начать свои продажи или усилить их максимально эффективным способом. По каждому из этих пунктов наша компания готова делиться опытом со стартапами.
Для нас успехом стартапа является момент, когда его продажи выросли не только в России, но и за рубежом. Мы редко общаемся с теми, кто нацелен только на одну страну.
В нашей модели сотрудничества есть несколько возможностей для стартапа — он либо будет продавать клиентам «Лаборатории», либо с помощью партнеров, которых у нас несколько десятков тысяч по всему миру, выйдет на новые рынки и страны, или станет прямым технологическим партнером «Лаборатории».
Скорее, при работе со стартапами мы нацелены не столько на финансовый результат, сколько на построение нового бизнеса. Даже, если мы не смогли по какой-то причине договориться со стартапом, то он все равно получил от нас обратную связь. И мы готовы с ним встречаться и дальше, смотреть на то, как меняется ситуация в его развитии.
«Лаборатория» не хочет быть просто инвестором. Когда что-то планируем получить взамен, то думаем, как это ляжет на стратегию стартапа, и просим то, что не помешает бизнес-развитию. В нашей области создание продукта – недешевый процесс, поэтому получение большой доли в стартапе может только помешать его развитию. Обычно мы начинаем работу со стартапом, не беря долю, просим его сделать несколько пилотов с нашей контактной сетью. Если у него получается, то вступаем во вторую стадию переговоров.
«Лаборатория» занимает роль советника – делится опытом изнутри. И, если у стартапа уже есть ощущение, что он игрок на корпоративной рынке, то ему надо изначально задуматься над тем, как он видит свой будущий успех с корпорацией.
Александр Ханин, Основатель и CEO VisionLabs
«Рекомендую стартапам говорить на языке клиентов»
У нас был очень долгий путь к первому контракту. Сейчас могу сказать, что у большинства проектов есть одна распространенная ошибка – они делают фокус на технологию. Но корпорации, фонды и конечных клиентов интересует совершенно другое – они хотят решения конкретной проблемы. Поэтому в работе с корпорациями рекомендую делать акцент на том, как решение или продукт компании поможет либо оптимизировать внутренние процессы в самой корпорации, либо дополнить линейку ее продуктов.
Наша первая крупная продажа состоялась после двух лет с момента старта компании. Инвесторов мы заинтересовали только тогда, когда накопили критическую массу продаж для корпоративных решений.
На первом этапе нам очень помог акселератор корпорации Intel. Мы его прошли в 2013 году в Университете Беркли. Там нам дали простое задание – провести 100 интервью с заказчиками из различных клиентских сегментов, которые упоминались у нас в презентации. Мы тогда говорили, что можем распознавать все, что движется. В итоге провели 120 интервью, протестировали несколько гипотез, и выбрали наиболее горячий сегмент – розничные банки, которые страдают от проблемы с мошенничеством. И на базе этой проблемы построили целую платформу.
После акселератора дела у проекта пошли намного лучше, с нами стали говорить на одном языке. Например, банкам мы сообщали, что готовы ускорить кредитный конвейер на столько-то процентов.
Сегодня у нас уже есть экспериментальная команда, которая занимается совершенно новыми проектами, и я не удивлюсь, если через год они будут генерировать половину выручки компании.
Анна Шакирова, Sistema Venture Capital
«Мы ищем звезды, а не сеем семена, которые помогают расти»
Отличие нашего фонда от «Лаборатории Касперского» в том, что мы существуем по правилам независимого венчурного фонда с отдельной стратегией, которая базируется не на стратегии АФК «Система». Идея не в том, чтобы развивать продуктовый ряд корпорации, а найти прорывные ниши и глобальные точки роста для нее. Но корпорация при этом не является заказчиком каких-то конкретных решений.
Ключевой критерий отбора стартапов – глобальный характер технологии. Но даже, если компания работает на внутреннем рынке, то мы ищем варианты выхода на внешние рынки. Особое внимание уделяем яркой команде, которая способна самостоятельно вести бизнес. Скорее, ищем звезды, чем сеем семена, которые помогают расти.
У нас есть как технологическая, так и инвестиционная экспертиза, наработанная в АФК «Система». Очень хорошо понимаем, какие факторы успеха существуют для бизнесов, которые переросли определенный масштаб. Понимаем, как выстроить HR, как смотреть на финансы, на прозрачность бизнеса. Используем связи, которые у нас есть. Но вести диалог с уже сложившимся бизнесом мы не готовы.
У нас в портфеле есть определенное количество проектов, где основатели – бывшие сотрудники корпораций. Внутри корпораций давать возможность развивать венчурные проекты не стоит, поэтому, кстати, в АФК «Система» и было принято решение, что они должны быть реализованы через отдельную структуру.
Данная публикация основана на видеозаписи выступлений спикеров на круглом столе в рамках ежегодного Форума технологических компаний GoTech 2016, размещенной в официальном аккаунте Gotech на Youtube.
Комментарии (0)
пятница, 10 февраля 2017 г.
Сравнение решений по балансировке высоконагруженных систем
Меня зовут Женя, я работаю в компании IPONWEB. Сегодня мы поговорим про развитие наших решений в балансировке высоконагруженных систем.
Сначала я пробегусь по понятиям, которыми буду оперировать. Начнём с того чем мы занимается: RTB, Real Time Bidding — показ рекламы с аукционом в реальном времени. Очень упрощенная схема того, что происходит, когда вы заходите на сайт:
Для того, чтобы показать рекламу, идет запрос на RTB-сервер, который запрашивает у рекламных серверов их ставки и потом решает, какую рекламу вам показать.
Особенности IPONWEB
У нас вся инфраструктура в облаках. Мы очень активно пользуемся Amazon и GCE, у нас несколько тысяч серверов. Главная причина, по которой мы полностью живем в облаках, — это скалируемость, то есть у нас реально часто нужно добавлять/удалять инстансы, иногда очень много.
У нас есть миллионы запросов в секунду, все эти запросы HTTP. Это может быть не до конца применимо к остальным протоколам. У нас очень короткие ответы. Может быть, и не очень, я думаю, в среднем от одного до несколько килобайт ответы. Мы не оперируем какими-то большими объемами информации, просто очень большим их количеством.
Для нас не актуально кэширование. Мы не поддерживаем CDN. Если нашим клиентам нужны CDN, то они сами занимаются этими решениями. У нас есть очень сильные суточные и событийные колебания. Они проявляются во время праздников, спортивных событий, каких-то сезонных скидок и т. д. Суточные можно хорошо посмотреть на таком графике:
Красный график — обычный стандартный график в европейской стране, а синий график — это Япония. На этом графике мы видим, что каждый день примерно в двенадцать у нас есть резкий скачок, и примерно в час трафик резко падает. Связано это с тем, что люди уходят на обед, и как очень порядочные японские граждане они пользуются интернетом активно больше всего на обеде. Такое очень хорошо видно на этом графике.
У нас есть большой разброс пользователей по всему миру. Это — вторая большая причина, почему мы пользуемся облаками. Нам очень важно отдать ответ быстро, и если сервера находятся в каких-то других регионах от пользователей, то часто это неприемлемо для RTB-реалий.
И у нас есть чёткое разграничение серверного и пользовательского трафика. Вернёмся к той схеме, которую я показал на первом слайде. Для простоты всё то, что приходит от пользователя до первичного RTB-сервера — это пользовательский трафик, все то что происходит за ним — это серверный трафик.
Зачем балансировать?
Две главные причины — это масштабируемость и доступность сервисов.
Масштабируемость — это когда мы добавляем новые сервера, когда мы из одного сервера, который уже не может справиться, вырастаем как минимум до двух, и между ними надо как-то раскидывать запросы. Доступность — это когда мы боимся, что с этим одним сервером что-то случится. Опять же нужно добавить второй, чтобы между ними как-то всё это балансировать и распределять запросы только по тем серверам, которые могут на них ответить.
Что ещё часто требуется от балансировщиков? Эти функции, естественно, для целей каждого приложения могут быть разными. Для нас больше всего актуально SSL Offload. Как это работает, показано здесь.
От пользователя до балансировщика идёт трафик, который зашифрован. Балансировщик расшифровывает его, раскидывает по бэкендам уже расшифрованный HTTP-трафик. Потом балансировщик обратно зашифровывает его и отдает пользователю опять в зашифрованном виде.
Ещё нам очень важна такая шутка, как Sticky-балансировка, которую часто называют session affinity.
Почему для нас это актуально? Когда мы видим несколько слотов для рекламы на странице, мы хотим, чтобы при открытии этой страницы все запросы пришли сразу на один бэкенд. Почему это важно? Есть такая особенность как roadblock. Она означает, что если мы показали в одном из слотов баннер, например, Pepsi, то в другом мы не можем показать баннер той же самой Pepsi или Coca-Cola, потому что это конфликтующие баннеры.
Нам нужно понимать, какие баннеры мы показываем в данный момент пользователю. При балансировке нам нужно удостовериться что пользователь пришел на один бэкенд. На этом бэкенде у нас создается некое подобие сессии, и мы понимаем какие именно рекламы можно показать этому пользователю.
Также у нас есть Fallback. На примере сверху виден баннер, на котором он не работает, а справа — баннер, на котором он работает.
Fallback — это ситуация, когда по каким-то причинам бэкенд не справляется, и дабы не сломать страницу пользователю, мы отдаем ему обычно совсем пустой пиксель. Здесь я его нарисовал большим зеленым прямоугольником для понимания, но на самом деле обычно это просто маленькая гифка, двухсотый HTTP Response и правильный набор header’ов, чтобы у нас не сломалось ничего в верстке.
Так у нас выглядит нормальная балансировка.
Синяя жирная линия — это сумма всех запросов. Эти маленькие линии, много-много, — это идут запросы на каждый инстанс. Как мы видим, тут балансировка достаточно хорошая: практически все они идут почти одинаково, сливаясь в одну линию.
Это — балансировка курильщика. Здесь что-то пошло не так.
Проблема — на стороне Amazon. Подобное, кстати, произошло совсем недавно, буквально две недели назад. С амазоновских балансеров трафик стал приходить в таком виде.
Стоит сказать, что метрики — это хорошо. Здесь Amazon до сих пор не верит нам, что у нас происходит что-то плохое. Они видят только вот этот общий график, в котором видно лишь сумму запросов, но не видно, сколько запросов приходит по инстансам. До сих пор с ними боремся, пытаемся им доказать, что что-то с их балансировщиком идет не так.
Балансировка DNS
Итак, про балансировку я расскажу на примере одного из наших проектов. Начнем собственно с самого начала. Проект был молодой, мы примерно около начала этого проекта.
Большая часть трафика в этом проекте — серверный. Особенность работы с серверным трафиком: мы можем легко решать некоторые проблемы. Если у нас есть специфика у одного из наших клиентов, то мы можем как-то договориться с ним, чтобы они что-то поменяли на своем конце, как-то обновили свои системы, что-то еще сделали, чтобы у нас лучше работало с ними. Можно выделить их в отдельный пул: мы можем просто взять одного клиента, у которого есть какая-то проблема, привязать его к другому пулу и решить проблему локально. Либо, в совсем тяжелых случаях, можно даже его забанить.
Первое, чем мы начали пользоваться, — это была обычная DNS-балансировка.
Пользуемся Round-robin DNS-пулами.
Каждый раз при запросе на DNS, пул ротируется, и сверху оказывается новый IP-адрес. Таким образом работает балансировка.
Проблемы обычного Round-Robin DNS:
- У него нет никаких проверок статуса. Мы не можем понять, что с бэкэндом случилось что-то не то и перестать отсылать на него запросы.
- У нас нет понимания геолокации клиента.
- Когда запросы идут с достаточно маленького количества айпишников, что актуально для серверного трафика, то балансировка может быть не очень идеальной.
Балансировка gdnsd
На помощь приходит gdnsd – это DNS сервер, который многие, наверное, знают, которым мы активно пользуемся и сейчас.
- Главная фича gdnsd, которой мы пользуемся, — это DYNA-записи. Это такие записи, которые выдают на каждый запрос, используя некий плагин, динамически либо одну A-запись, либо набор A-записей. Там внутри они могут использовать Round-robin.
- gdnsd умеет поддерживать базы данных geoIP.
- У него есть проверка статуса. Он может по TCP какие-то запросы посылать хосту, по HTTP смотреть Response и выкидывать из пула те серверы, которые не используются, в которых в данный момент есть какая-то проблема.
Чтобы поддерживать динамичность этих записей, нам нужно поддерживать довольно низкий TTL. Это сильно увеличивает трафик на наши DNS-сервера: довольно часто клиентам приходится перезапрашивать эти пулы, и поэтому DNS-серверов, соответственно, приходится иметь больше.
Через какой-то промежуток времени мы сталкиваемся с проблемой 512 байт.
Проблема 512 байт — это проблема практически всех DNS-серверов. Изначально, когда DNS только проектировался, максимальный MTU у модемов был 576 байт. Это 512 байт + 64 длины заголовка. Пакеты от DNS исторически не отсылают больше чем 576 байт по UDP.
Соответственно если у нас пул длиннее, чем 512 байт, то мы отсылаем только часть пула, включаем в нем флаг truncated. Потом уже от клиента приходит запрос по TCP, переспрашивая нас опять это пул. И тогда мы присылаем ему полный пул, теперь уже по TCP.
Эта проблема была только у части наших клиентов, примерно у 15%. Мы смогли выделить их отдельно в пул и использовать для них weighted-пулы в gdnsd.
Бонус weighted-пулов в этом случае — их можно разбивать. Если у нас, скажем, 100 серверов, мы их разбиваем на 5 частей. Мы отдаем на каждый запрос один из этих маленьких подпулов, в которых всего 20 серверов. И Round-robin проходит по этим маленьким пулам, каждый раз он выдает новый пул. Внутри самого пулa тоже используется Round-Robin: он шаффлит эти айпишники и каждый раз выдает новые.
Веса gdnsd можно использовать помимо этого для, например, staging серверов. Если у вас есть более слабый инстанс, вы на него можете изначально отсылать гораздо меньше трафика и проверять, что что-то там сломалось, только на нём, отсылая на него достаточно маленький набор трафика. Или если у вас есть разные типы инстансов, либо вы используете разные сервера. (Я часто говорю «инстансы» потому что у нас всё в облаках, но для вашего конкретного случая это может быть не так.) То есть вы можете использовать разные типы серверов и с помощью gdnsd слать на них больше или меньше трафика.
Тут у нас тоже возникает проблема — DNS-кэширование. Часто, когда идет запрос этого пула, мы отдаем только маленький пул, и этот пул кэшируется. С этим пулом у нас продолжает жить какой-то клиент, не переспрашивая наш DNS. Такое случается, когда DNS-клиент плохо себя ведет, не придерживается TTL и работает только с маленьким ограниченным набором IP-адресов, не обновляя его. Если он получил изначально полный лист по TCP, это нормально. Но если он получил только маленький пул, который weighted, то это может быть проблемой.
Через какое-то время мы сталкиваемся с новой проблемой.
Те оставшиеся 85% серверного трафика всё ещё пользуются обычными мультифопулами, как это называется в gdnsd. С некоторыми из них начинаются проблемы.
Мы поняли, что проблема происходит только у амазоновских DNS. То есть у тех наших клиентов, которые сами хостятся в Amazon, при ресолве пула, в котором находится больше 253 хостов, им просто приходит ошибка NXDOMAIN, и они полностью не ресолвят этот целый пул.
Это произошло тогда, когда мы добавили около 20 хостов, и у нас их стало 270. Мы локализовали число до 253, мы поняли, что на этом количестве становится проблемно. Сейчас эту проблему уже починили. Но на тот момент мы поняли, что топчемся на месте, и надо как-то эту проблему решать дальше.
Так как мы находимся в облаках, первое, о чем мы подумали, — попробовать вертикальное масштабирование. Оно сработало, соответственно, мы сократили количество инстансов. Но опять же, это временное решение проблемы.
ELB
Мы решили попробовать что-то еще, тогда выбор пал на ELB.
ELB – это Elastic Load Balancing, решение от Amazon, которое балансирует трафик. Как это работает?
Они предоставляют вам CNAME, в данном случае это вот эта страшная строчка под www.site.com
: elb, цифры, регион и так далее. И этот CNAME ресолвится на несколько внутренних айпишников инстансов, которые балансируют на наши бэкенды. В таком случае нам нужно всего один раз привязать их CNAME в нашем DNS к нашему пулу. Потом мы уже добавляем в группу серверы, на которые раскидывают балансировщики.
ELB умеет SSL Offload, к нему можно прицепить сертификат. Также он умеет HTTP status checks, чтобы понимать, насколько живые у нас инстансы.
Мы практически сразу стали сталкиваться с проблемами с ELB. Есть такая шутка как прогрев балансировщиков ELB. Это нужно, когда вы хотите пускать больше 20—30 тысяч запросов в секунду. Прежде чем перевести весь ваш трафик на ELB, вам нужно написать письмо в Amazon, сказать, что мы хотим пустить много трафика. Вам присылают письмо с кучей страшных вопросов про характеристики вашего трафика, сколько, когда, как долго вы собираетесь это всё поддерживать. Затем они добавляют новых инстансов в свой пул и готовы к наплыву трафика.
И даже при предварительном прогреве мы столкнулись с проблемой. Когда мы у них запросили 40 тысяч запросов в секунду, примерно на 30 тысячах у них все сломалось. Нам пришлось все это дело быстро откатывать.
Ещё у них есть балансировка по скорости ответов. Это алгоритм работы амазоновского балансировщика. Он смотрит насколько быстро ваши бэкенды отвечают. Если он видит, что быстро, то шлет туда больше трафика.
Проблема здесь в чем? Если ваш бэкенд отчаянно пятисотит [выдаёт код состояния HTTP 5XX, что говорит об ошибке сервера] и не справляется, то балансировщик думает, что бэкэнд очень быстро отдает ответы, и начинает слать ему ещё больше трафика, загибая ваш бэкэнд ещё сильнее. В наших реалиях это ещё проблемнее, потому что мы, как я уже рассказывал, обычно отсылаем 200-ый ответ, даже если всё плохо. Пользователь не должен видеть ошибку — просто посылаем пустой пиксель. То есть для нас эту проблему решить ещё сложнее.
На последней конференции Amazon они рассказывали, что если у вас что-то плохое происходит, то в exception’ы заворачивайте какие-нибудь таймауты по 100-200 мс, искусственно замедляя 500-ые ответы, чтобы амазоновской балансировщик понимал, что ваш бэкенд не справляется. Но вообще, по-хорошему надо делать правильные status checks. Тогда ваш бэкенд понимал бы, что есть проблемы, и отдавал бы на status checks проверки проблему, и его просто выкидывало бы из пула.
Теперь у Amazon появилось новое решение: Application Load Balancer (ALB). Это довольно интересное решение, но нам оно не сильно актуально, потому что оно для нас ничего не решает и скорее всего будет стоить гораздо больше. Их система с хостами стала сложнее.
Но ALB поддерживает Path-based routing: это значит, что если у вас, например, пользователь приходит на /video
, то вы можете перенаправлять запрос на один набор инстансов, если на /static
, то на другой, и так далее.
Есть поддержка WebSocket, HTTP/2 и контейнеров. Если у вас Docker внутри одного инстанса, то он может распределять между ними.
GLB
В Google мы пользуемся GLB. Это довольно интересное решение. По сравнению с Amazon, у него есть много преимуществ.
Первое — у нас всего 1 IP. Когда вы создаете балансировщик в Google, вам даётся единственный айпишник, который вы можете привязать к своему сайту. Это значит, что вы можете привязать его даже к «голому» домену. CNAME же можно привязать к домену второго уровня.
Вам нужно создать всего один балансировщик по всем регионам. То есть в Amazon нам нужно в каждом регионе создавать по балансировщику, чтобы балансировать между инстансами внутри этого региона, а в Google — всего один балансировщик, всего один один IP, и он балансирует между всеми вашими инстансами по разным регионам.
Гугловский балансировщик умеет Sticky и по IP, и по cookie. Я рассказывал, зачем нужен Sticky — нам нужно одного пользователя переслать на один бэкэнд. Амазоновские балансировщики умеют только по cookie, то есть они сами на уровне балансировщика выдают cookie. Потом его проверяют, и если видно, что у пользователя cookie, соответствующий одному из инстансов, его отсылают на тот же самый. Гугловский умеет IP, что нам гораздо лучше, хоть и не всегда решает все проблемы.
У балансировщика Google есть Instant warm-up: его не надо никак прогревать, на него сразу можно отсылать до миллиона запросов. Миллион запросов — это то, что они обещают сами точно, и то, что я сам проверял. Думаю, дальше они растут как-то сами внутри.
Но при этом у них есть проблема с резким изменением числа бэкендов. В какой-то момент мы добавили около 100 новых хостов. На этом этапе гугловский балансировщик загнулся. Когда мы стали общаться с инженером из Google, они сказали: добавляйте по одному в минуту, тогда будет вам счастье.
Также недавно в их HTTP-балансировщике подрезали порты, которыми вы можете пользоваться при создании балансировщика. Раньше было обычное поле для ввода, сейчас можно выбрать только между 80 и 8080 портами. Для кого-то это может быть проблемой.
Ни у одного из этих облачных балансировщиков нет поддержки SNI. Если вам нужно поддерживать несколько доменов и несколько сертификатов, то вам нужно на каждый сертификат создавать по отдельному балансировщику и к нему привязывать. Это решаемая проблема, но такая ситуация может быть неудобной.
Комментарии (0)
[Из песочницы] Home Assistant или еще один «мозг» для проекта типа «Умный Дом»
Читая буржуйские форумы (справедливости ради, нужно отметить, человек я повернутый на автоматизации и IoT), обратил внимание на доселе мне неизвестное нечто, что называют Home Assistant (HASS), эту систему умельцы-то и прикручивают к GH.
В двух словах о самой платформе:
Система написана на Phyton, последний релиз был 29 января, текущая версия: 0.37.0
Поддерживаемые ОС:
- Windows 10
- Mac OS X
- Ubuntu 14.04
- Raspbian (Raspberry PI)
- iOS App – beta
Поддерживаемые компоненты: 545 шт., включая почти все TV/AV receivers, Broadlink, ZigBee, iCloud, Yandex TTS и многое, многое другое.
Для запуска, подключения и настройки компонентов, выключателей, сценариев, групп, триггеров совершенно не обязательно знать Phyton, но нужно хоть немного знать yaml.
«Чёйта вдруг?» — подумаете вы.
Отвечаю: настройка всего вышеперечисленного (существующих компонентов) осуществляется исключительно через YAML файл (“configuration.yaml”).
Установка простая – останавливаться на ней и расписывать все шаги смысла не имеет, к тому же у проекта имеется шикарное сообщество, готовое помочь в трудную минуту (Eng). (все ссылки дополнительно помещу в подвале, как предписывает устав)
Будьте готовы, что установка занимает немало времени, на моей RPi 3 общее время (без Raspbian) заняло около часа.
После завершения заветного wget
, я приступил к изучению платформы. Установка HASS производится в директорию: /home/homeassistant
. Нас же интересует /home/homeassistant/.homeassistant/configuration.yaml
.
По умолчанию в конфиге присутствует такой компонент:
# Автоопределение устройств
discovery:
Если у вас в сети имеются такие устройства:
- Google Chromecast
- Belkin WeMo switches
- Philips Hue
- Netgear routers
- Plex media server
- Panasonic Viera
- Roku media player
- Sonos Speakers
- Yamaha media player
- Logitech media server (Squeezebox)
- DirecTV
Они будут обнаружены автоматически и отображены в основной (и пока единственной) группе «Home».
Компонент «http»:
# Включение доступа к frontend
http:
# Убрать тэг комментария и выставить пароль (recommended!)
#api_password: YOUR_PASSWORD
Как абсолютно справедливо отмечено на портале — HIGHLY recommended – из соображений безопасности.
После успешного изменения конфигурационного файла, необходимо перезапустить сервис. Для этого есть два пути:
1) Использовать shell команды:
~ $ sudo systemctl stop home-assistant.service
~ $ sudo systemctl start home-assistant.service
или сразу
~ $ sudo systemctl restart home-assistant.service
2) Выполнить перезапуск из GUI: Развернуть гамбургер, в подвале “Developer Tool” открыть “Services”, в выпадающем списке “Domain” выбрать «homeassistant», в выпадающем списке “Service” – «Restart» и нажать кнопку «CALL SERVICE».
Обращу ваше внимание, что frontend использует кэширование, во время перезапуска или остановки сервиса, страница полностью доступна для просмотра, однако никаких действий осуществить не представляется возможным.
Как только запущен рестарт сервиса, либо через shell либо через GUI, в нижней части экрана отобразится поле показывающее текущий статус сервиса.
Как только сервис поднимется, страница автоматически обновится и поле исчезнет. Если в конфиг закралась ошибка, сервис так и не запустится.
Что необходимо сделать в таком случае:
1) Открыть лог файл: ~/.homeassistant/ home-assistant.log
Записи в логе довольно структурированы, с зачастую, указанием номером строки в configuration.yaml в которой возникла ошибка.
2) Решить проблему указанную в логе
3) Запустить сервис командой выше из консоли
Мы задали пароль, мы выставили (если по какой-то причине не было по умолчанию) автоопределение оборудования, пришло время зайти на портал:
http://IP-Address:8123
8123 — порт по умолчанию.
Что позволяет сделать HASS с ресивером:
Нам доступны: Источник, Громкость, Без Звука (при прослушивании аудио записей, доступны кнопки управления треками).
Теперь же, более подробно рассмотрим yaml-ку. Я привожу несколько расширенную версию, на базе которой будет проще понять какие возможности у HASS есть, а также, возможно, поможет вам в настройке собственного окружения.
homeassistant:
# Название окружения запущенного HASS
name: Дом
# Координаты для Зоны Дом*, а также для расчета рассвета и заката
latitude: _REDACTED_
longitude: _REDACTED_
# Высота над уровнем моря – для расчета рассвета и заката
elevation: 0
# 'metric' измерения в метрической, 'imperial' - имперской
unit_system: metric
# Часовой пояс: http://ift.tt/11PilBG
time_zone: Europe/Moscow
# К описанию элемента customize вернемся позже. Здесь - подключение файла в котором описан компонент
#customize: !include customize.yaml
# Включение frontend GUI. Чтобы скрыть, необходимо закомментировать
frontend:
# Проверка доступных обновлений
updater:
reporting: no
# Отслеживание изменений показателей подключенных устройств и компонентов
logbook:
# Отслеживание закатов и рассветов по гео-координатам и высотой над уровнем моря
sun:
# надиктовывание команд из фронтенд GUI
conversation:
# отслеживание истории показателей
history:
# Автоопределение совместимых устройств
discovery:
# Настройка доступа к ФронтЕнду:
http:
api_password: _REDACTED_
# ssl_certificate: _REDACTED_ # SSL опционально
# ssl_key: _REDACTED_
# base_url: _REDACTED_
# trusted_networks:
# - 127.0.0.1
# - _REDACTED_/24
# ip_ban_enabled: True
# login_attempts_threshold: 5
Зона Дом* — HASS позволяет создавать зоны (локации) по миру, стране, городу (где угодно) на основе гео-координат, для использования их в последующем при создании «автоматизаций» (automation) и оповещений (notify).
Забегая вперед, HASS поддерживает интеграцию с сервисом Telegram, на базе которого я реализовал оповещение, но об этом чуть позже.
Далее нужно отредактировать конфиг файл, перезапустить сервис HASS, перейти на Web страницу и посмотреть, что получилось.
Теперь приступим к первой автоматизации (далее automation). В качестве первой automation, предлагаю рассмотреть «Будильник». Для реализации этой задачи нам не обязательно иметь аудио или видео устройства в сети. Однако, в примере, я покажу использование ресивера в качестве самого будильника.
Задача
Пробуждать в назначенное время, включением AV ресивера, с нарастанием громкости. По истечению определенного времени, синтезировать текст в речь и оповещать просыпающихся о текущей погоде за окном, дабы оделись в соответствии с погодными условиями, также отправка сообщения в Telegram, с информацией о погоде.
Ресурсы
- Raspberry Pi
- Home Assistant
- Сеть (wired/wireless) с выходом в Internet
- VLC
- AV Ресивер (опционально)
- Telegram
- Telegram Bot
- Yandex SpeechKit Cloud
- OpenWeatherMap
Реализация
Для начала нам нужно создать бота в Telegram, для его подключения к нашему проекту. В интернете много инструкций о там, как зарегистрировать собственного бота, поэтому описывать данный процесс не буду. Лишь по ходу описания, буду заострять ваше внимание на важных моментах.
Итак, у нас есть свой Бот. Для его подключения к HASS необходимо в configuration.yaml
прописать следующее:
# Telegram Notifier
notify:
- name: NOTIFIER_NAME (имя которое впоследствии будет использоваться для идентификации компонента ‘notify’ - eng)
platform: telegram
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
chat_id: YOUR_CHAT_ID
Однако если планируете добавить несколько сервисов оповещения, лучше использовать вложенный файл. Значительно упрощает читаемость конфигурационного файла.
О том как это сделать будет написано ниже.
Как видно из комментариев, нам необходимо API выданное нам для Бота, а также, Chat ID. Для того, что бы его получить Chat ID, нужно написать вашему Боту хотя бы одно сообщение, после чего открыть страницу с адресом:
http://ift.tt/2l1tvnk.
Где *API* — API выданное вам.
В результате вы получите некий JSON, в котором нас интересует следующее:
{"<b>id</b>":<b>123456789 </b>…}
Значение ID нам и нужно.
Далее, подключим компонент отвечающий за синтезирование текста — SpeechKit Cloud Yandex.
Для этого нам необходимо зарегистрироваться и пройти несколько несложных шагов настройки.
Выбрать из подключаемых сервисов API SpeechKit Cloud и получить ключ.
В configuration.yaml
внести следующую запись:
tts:
- platform: yandextts # Определение платформы по работе с компонентом TTS
name: yandextts # Имя компонента, для использования в последующем
api_key: 'API к SpeechKit'
language: 'ru-RU' # Язык произносимой фразы – по умолчанию ru
codec: 'mp3' # Формат генерируемого аудио файла
voice: 'jane' # Голос диктора
emotion: 'good' # Настроение диктора
speed: '1' # Скорость речи
Пояснения и варианты параметров доступны на сайте Яндекс.
Теперь у нас есть аж два сервиса по оповещению, но нам «маловато будет», придется добавить еще один: VLC.
Так как я изначально использовал Raspberry PI, VLC я устанавливал простой командой:
sudo apt-get install vlc
Далее, нужно настроить параметры звука на Raspberry — устройство воспроизведения по умолчанию и проделать нехитрую манипуляцию с правами доступа пользователя, который был создан при установке HASS AIO (All-In-One).
sudo usermod -a -G audio homeassistant
Команда добавляет пользователя в группу audio.
В configuration.yaml
вносим строку:
media_player: !include media_player.yaml
В родительской папке создаем файл:
media_player.yaml
где будут храниться все настройки для медиа устройств которые мы будем подключать.
Вносим следующие настройки:
- platform: yamaha
name: Yamaha_671
zone: 2
# Определяем кастомные элементы управления - опционально
commands:
turn_on:
service: media_player.turn_on
turn_off:
service: media_player.turn_off
volume_up:
service: media_player.volume_up
volume_down:
service: media_player.volume_down
customize: # изменяем отображение на frontend данного устройства
media_player.yamaha_671:
hidden: true # скрываем устройство yamaha_671 - зона основная (Main)
- platform: vlc
name: vlcmp # название нашего плеера VLC
У моего ресивера имеется две зоны воспроизведения (Main, Zone 2). В примере я использую второю.
Все дополнительные компоненты подключены. Можем переходить к настройке самого элемента будильника.
Добавим в конфигурационный файл строку:
## Input Boolean
input_boolean: !include input_boolean.yaml
Создаем файл в родительской папке:
input_boolean.yaml
Вносим следующие строки:
alarmweekday: #создаем переключатель - Будить только в рабочие дни
name: Рабочая неделя
initial: on # Значение по умолчанию
icon: mdi:calendar
Как видно из названия yaml, мы подключаем компонент типа «Выключатель». Единственное что мне кажется дополнительно стоит описать это icon. Мы можем использовать любые иконки из библиотеки MDI.
Для выставления времени можно использовать компоненты:
input_slider
input_select
Один представляет собой выбор из списка. Другой слайдер. Я воспользовался удобным в настройке слайдером.
В configuration.yaml
прописываем:
## Input Slider
input_slider: !include input_slider.yaml
Как уже завелось в родительской папке создаем файл:
input_slider.yaml
Далее заполняем его:
alarmhour:
name: Часы
icon: mdi:timer
initial: 8 # значение по умолчанию
min: 0 #Минимальное значение
max: 23 #Максимальное значение
step: 1 #Шаг изменения
alarmminutes:
name: Минуты
icon: mdi:timer
initial: 40
min: 0
max: 59
step: 1
И еще одну нехитрую штуку нам предстоит сделать: сенсор.
В конфиге прописываем:
sensor: !include_dir_merge_list sensors
Эта команда означает, что брать следует все файлы из папки sensors. В свою очередь, в папке sensors создаем yaml файл с названием:
alarmclock.yaml
. Настройка alarmclock.yaml
:
- platform: template
sensors:
alarm_time:
friendly_name: 'Будильник '
value_template: ':'
Тут интереснее. Появляется некий template. Этот компонент позволяет всячески управлять данными других компонентов HASS. В данном примере, мы создаем сенсор и заполняем его данными из слайдеров будильника, принудительно приводя значение к int и добавляя «0» если «длина» минут == 1. Более подробно о возможностях можно узнать на портале HASS.
Нам потребуется еще один сенсор — погодные условия.
Среди всех доступных компонентов типа weather мой выбор пал на OpenWeatherMap Sensor. В папке sensors необходимо создать файл weather.yaml
и наполнить его следующим:
- platform: openweathermap
api_key: *API*
latitude: *latitude*
longitude: *longitude*
monitored_conditions:
- weather
- temperature
- wind_speed
- humidity
- clouds
- rain
- snow
Как не трудно заметить, нам потребуется для интеграции API от OWM. API бесплатное, и получить его можно зарегистрировавшись на портале. Сохраняем, закрываем, идем дальше.
Выключатели, слайдеры и сенсоры для будильника мы создали. Только чего ж включать, если самого будильника пока нет?
Приступим к созданию automation.
В configuration.yaml
прописываем:
## Automation for: alarmclock...
automation: !include_dir_merge_list automation
Это значит, что будут подгружены все файлы из папки automation. Теперь создаем в родительской директории папку «automation».
Создаем файл: alarmclock.yaml
И приступаем к заполнению.
Так как у меня используется инкрементирование громкости каждую секунду — файл большой. Я приведу самые необходимые строки, остальное можно настроить по аналогии.
- alias: 'Будильник '
trigger:
platform: template
value_template: '' # Определение тригера.
condition: # Условия
condition: or
conditions:
- condition: and
conditions:
- condition: state
entity_id: input_boolean.alarmweekday
state: 'on'
- condition: time
weekday:
- mon
- tue
- wed
- thu
- fri
- condition: state
entity_id: input_boolean.alarmweekday
state: 'off'
action: # Действия
- service: notify.NOTIFIER_NAME
data_template:
title: Доброе утро, Дружище! =)
message: "Просыпайся, на работу пора! За окном сейчас °C."
- service: media_player.turn_on # Включаем медиа плеер
entity_id: media_player.yamaha_671_zone_2 # Указываем, какой именно медиа плеер нас интересует
- service: media_player.volume_set # Устанавливаем громкость
data:
entity_id: media_player.yamaha_671_zone_2# Указываем, какой именно медиа плеер нас интересует
volume_level: '0.20' # Значение параметра Громкость
- service: media_player.select_source # Выбираем источник
data:
entity_id: media_player.yamaha_671_zone_2
source: NET RADIO
- delay: 00:00:10 # Пауза
- service: media_player.volume_set # Устанавливаем громкость
data:
entity_id: media_player.yamaha_671_zone_2
volume_level: '0.25'
- delay: 00:00:01 # Пауза
- service: media_player.volume_set # Устанавливаем громкость
data:
entity_id: media_player.yamaha_671_zone_2
volume_level: '0.30'
# Тут было много повторяющихся блоков с растущим значением громкости каждую секунду
- delay: 00:00:01
- service: media_player.turn_on # Включаем опять, вдруг выключен. (В идеале лучше выносить в отдельный automation)
entity_id: media_player.yamaha_671_zone_2
- service: media_player.select_source
data:
entity_id: media_player.yamaha_671_zone_2 # Выбираем источник - теперь это вход Audio 2 - именно к нему подключен RPi
source: AUDIO2
- service: media_player.volume_set
data:
entity_id: media_player.yamaha_671_zone_2
volume_level: '0.70'
- delay: 00:00:01
- service: tts.yandextts_say # Вызываем Яндекс
data_template:
message: "Любезный сударь! Извольте выслушать краткую сводку новостей о погоде. За окном сейчас градусов." # Указываем какой текст нам необходимо синтезировать
entity_id: media_player.vlcmp # Указываем, какой медиа плеер должен воспроизвести поток
language: 'ru-RU'
«Ура! Заработало!». Но чтобы все сделать красиво, предлагаю совершить еще одно несложное действие. Создать группы (вкладка на фронтенде).
Создаем файл group.yaml
в родительской папке. В конфиге ссылаемся на него:
group: !include group.yaml
И приступаем к заполнению
group.yaml
.
# Определяем какие вкладки и элементы будут отображаться во frontend, а также, что будет отображаться на основной вкладке.
default_view:
view: yes
entities:
- group.AlarmClock
- sensor.alarm_time
- sensor.owm_cloud_coverage
- sensor.owm_condition
- sensor.owm_humidity
- sensor.owm_rain
- sensor.owm_snow
- sensor.owm_temperature
- sensor.owm_wind_speed
#Определяем какие элементы входят в состав группы
alarmclock:
name: Будильник.
entities:
- sensor.alarm_time
- input_slider.alarmhour
- input_slider.alarmminutes
- input_boolean.alarmweekday
#Вкладка (tab) описываем состав вкладки.
AlarmClock:
name: Будильник
view: yes
entities:
- group.alarmclock
Настала пора сохранить все наши настройки, перезапустить сервис и посмотреть, что получилось!
На данный момент у меня подключены следующие компоненты:
TV, AV Ресивер, роутер TP-Link, ведется отслеживание устройств, оповещение меня и жены, когда кто-то из нас пришел\вышел из дома, автовключение ресивера, когда кто-то появляется дома первый, выключение устройств при выходе всех из дома, временно: Broadlink + Livolo Switch.
Развитие
- Подключиться к DIY выключателям света
- Попытаться подключиться к кофемашине и чайнику
- Сделать кнопки у кровати для более простого выключения будильника!
- Создать более оптимальные «автоматизации»
Дорогой читатель, я благодарен тебе, за твое бесценное время! Если к HASS будет живой интерес, опишу и другие возможности с примерами. До новых встреч!
Комментарии (0)