...

суббота, 11 февраля 2017 г.

Исследование защиты ArtMoney. Часть первая

Automount afuse

Читаем tar за 26 строк ANSI C кода

Ещё один Brainfuck интерпретатор

Brainfuck — язык программирования, созданный с одной целью: написать для него интерпретатор. Их было написано так много, что даже не буду давать на них ссылки. В этой статье на пальцах объясняется простой, но эффективный способ его оптимизации.


quine



Для оптимизации нужно выполнить 3 правила:


  1. Обратные инструкции (+ и -, > и <) взаимоуничтожаются, когда идут одна за другой. Код >++>><<- превращается в >+. Это, скорее, защита от дурака, чем оптимизация, т.к. такой код бессмысленен.


  2. Повторяющиеся инструкции заменяются на команды, в аргументах которых сказано сколько раз конкретную инструкцию выполнить. Например, код +++ заменяется на команду ADD с аргументом 3, а код <<< на MOVE:-3.


  3. Добавляется новая команда, у которой в bf нет соответствующий инструкции. Команда присвоения значения. Код [+] и [-] обнуляет текущую ячейку, а команда ADD за этим кодом присваивает текущей ячейке значение своего аргументы. Код --[-]+++ заменяется на команду ASSIGN:3.

Список команд с описанием


Каждая команда имеет свой аргумент (далее просто arg). Значение аргумента ограничено только у команды ADD, т.к. размер одной ячейки 256.


Имя команды Инструкции Описание
ADD + и - Изменяет значение текущей ячейки на arg
MOVE > и < Изменяет индекс текущей ячейки на arg
READ , Пропускает из потока ввода arg символов и читает следующий за ними символ
PRINT . Печатает символ соответствующий значению текущей ячейки 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

Комментарии (0)

    Let's block ads! (Why?)

    PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx

    Современные ПЛИС содержат мультигигабитные линия связи и существует большое количество протоколов для обмена. Однако при ближайшем рассмотрении применять стандартные протоколы в ПЛИС не всегда удобно. Например для ПЛИС Xilinx доступны реализации PCI Express, RapidIO, Aurora; У каждого из них есть недостатки. PCI Express и RapidIO работают с кодировкой 8/10 что сразу ограничивает пропускную способность. Aurora может работать с кодировкой 64/66 но не обеспечивает восстановление данных после сбоя. С учётом недостатков стандартных протоколов и особенностей применения я решил реализовать свой протокол обмена.

    Название 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

    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

    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. Генератор реализует три режима:

    1. Пропустить поток данных без изменений
    2. Подставить тестовую последовательность при сохранении скорости потока данных
    3. Подставить тестовую последовательность и передавать данные на полной скорости

    На этом генераторе основано тестирование в составе реальных проектов. Этот генератор не занимает много места в ПЛИС, он есть во всех проектах и позволяет в любой момент провести проверку канала обмена.

    Компоненты 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_tc_summary.log
    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 — проверка передачи пакета и внесение одиночной ошибки в процесс передачи.
    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 и проверяет принятый поток данных. При необходимости — вносит ошибки в процесс передачи.
    Сам компонент здесь:
    prq_transceiver_tb
    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;
    
    

    Результат работы теста:

    tc_00_1.log
    # 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)

      Let's block ads! (Why?)

      [Из песочницы] Агрессивная экономия энергии при работе от батареи на Windows

      Корпорации и стартапы: модели успешного сотрудничества

      Фонд развития инноваций GoTech продолжает рассказывать о том, каким образом корпорации видят успешное сотрудничество со стартапами в российском и международном масштабе.

      Юрий Васильев, директор «Русское техническое общество»

      «Корпорация – это путевка для «пилотирования»

      Если стартап столкнулся с рынком через корпорацию, то самое главное для него — это возможность получить пилотную площадку с обратной связью от рынка.

      Например, к нам обратился проект по аэромониторингу строительных площадок. Ни мы, ни они до конца на тот момент не понимали, какой будет конечная технология. Чтобы предоставить им тестовую площадку (конкретный микрорайон), мне пришлось пройти пять ступеней согласований, вплоть до охранников территории. Надо помнить, что как только ты спускаешься вниз, то никому там уже ничего не нужно. С инновационными технологиями идет вмешательство в спокойную жизнь большой корпорации. И в этом смысле корпорация, с которой вы работаете, является вашей путевкой для «пилотирования». На входе необходимо отработать свой полуфабрикат с реальными экспертами, с рынком, без каких-либо обещаний.

      За три месяца работы с проектом мы предоставили экономию в 22 млн рублей. Сейчас уже создано СП с прицелом на ближневосточный регион.

      Вартан Минасян, руководитель направления по инвестициям и инновациям «Лаборатории Касперского»

      «Мы редко общаемся со стартапами, которые целятся только в одну страну»

      «Лаборатория Касперского», как один из мировых экспертов в области информационной безопасности, заинтересована в том, чтобы на рынке появлялись новые продукты в этой сфере. Стартапам, со своей стороны, важно понимать, что за продукт надо создать, как его правильно сделать, как начать свои продажи или усилить их максимально эффективным способом. По каждому из этих пунктов наша компания готова делиться опытом со стартапами.

      Для нас успехом стартапа является момент, когда его продажи выросли не только в России, но и за рубежом. Мы редко общаемся с теми, кто нацелен только на одну страну.

      В нашей модели сотрудничества есть несколько возможностей для стартапа — он либо будет продавать клиентам «Лаборатории», либо с помощью партнеров, которых у нас несколько десятков тысяч по всему миру, выйдет на новые рынки и страны, или станет прямым технологическим партнером «Лаборатории».

      Скорее, при работе со стартапами мы нацелены не столько на финансовый результат, сколько на построение нового бизнеса. Даже, если мы не смогли по какой-то причине договориться со стартапом, то он все равно получил от нас обратную связь. И мы готовы с ним встречаться и дальше, смотреть на то, как меняется ситуация в его развитии.

      «Лаборатория» не хочет быть просто инвестором. Когда что-то планируем получить взамен, то думаем, как это ляжет на стратегию стартапа, и просим то, что не помешает бизнес-развитию. В нашей области создание продукта – недешевый процесс, поэтому получение большой доли в стартапе может только помешать его развитию. Обычно мы начинаем работу со стартапом, не беря долю, просим его сделать несколько пилотов с нашей контактной сетью. Если у него получается, то вступаем во вторую стадию переговоров.

      «Лаборатория» занимает роль советника – делится опытом изнутри. И, если у стартапа уже есть ощущение, что он игрок на корпоративной рынке, то ему надо изначально задуматься над тем, как он видит свой будущий успех с корпорацией.

      Александр Ханин, Основатель и CEO VisionLabs

      «Рекомендую стартапам говорить на языке клиентов»

      У нас был очень долгий путь к первому контракту. Сейчас могу сказать, что у большинства проектов есть одна распространенная ошибка – они делают фокус на технологию. Но корпорации, фонды и конечных клиентов интересует совершенно другое – они хотят решения конкретной проблемы. Поэтому в работе с корпорациями рекомендую делать акцент на том, как решение или продукт компании поможет либо оптимизировать внутренние процессы в самой корпорации, либо дополнить линейку ее продуктов.

      Наша первая крупная продажа состоялась после двух лет с момента старта компании. Инвесторов мы заинтересовали только тогда, когда накопили критическую массу продаж для корпоративных решений.

      На первом этапе нам очень помог акселератор корпорации Intel. Мы его прошли в 2013 году в Университете Беркли. Там нам дали простое задание – провести 100 интервью с заказчиками из различных клиентских сегментов, которые упоминались у нас в презентации. Мы тогда говорили, что можем распознавать все, что движется. В итоге провели 120 интервью, протестировали несколько гипотез, и выбрали наиболее горячий сегмент – розничные банки, которые страдают от проблемы с мошенничеством. И на базе этой проблемы построили целую платформу.
      После акселератора дела у проекта пошли намного лучше, с нами стали говорить на одном языке. Например, банкам мы сообщали, что готовы ускорить кредитный конвейер на столько-то процентов.

      Сегодня у нас уже есть экспериментальная команда, которая занимается совершенно новыми проектами, и я не удивлюсь, если через год они будут генерировать половину выручки компании.

      Анна Шакирова, Sistema Venture Capital

      «Мы ищем звезды, а не сеем семена, которые помогают расти»

      Отличие нашего фонда от «Лаборатории Касперского» в том, что мы существуем по правилам независимого венчурного фонда с отдельной стратегией, которая базируется не на стратегии АФК «Система». Идея не в том, чтобы развивать продуктовый ряд корпорации, а найти прорывные ниши и глобальные точки роста для нее. Но корпорация при этом не является заказчиком каких-то конкретных решений.

      Ключевой критерий отбора стартапов – глобальный характер технологии. Но даже, если компания работает на внутреннем рынке, то мы ищем варианты выхода на внешние рынки. Особое внимание уделяем яркой команде, которая способна самостоятельно вести бизнес. Скорее, ищем звезды, чем сеем семена, которые помогают расти.

      У нас есть как технологическая, так и инвестиционная экспертиза, наработанная в АФК «Система». Очень хорошо понимаем, какие факторы успеха существуют для бизнесов, которые переросли определенный масштаб. Понимаем, как выстроить HR, как смотреть на финансы, на прозрачность бизнеса. Используем связи, которые у нас есть. Но вести диалог с уже сложившимся бизнесом мы не готовы.

      У нас в портфеле есть определенное количество проектов, где основатели – бывшие сотрудники корпораций. Внутри корпораций давать возможность развивать венчурные проекты не стоит, поэтому, кстати, в АФК «Система» и было принято решение, что они должны быть реализованы через отдельную структуру.

      Данная публикация основана на видеозаписи выступлений спикеров на круглом столе в рамках ежегодного Форума технологических компаний GoTech 2016, размещенной в официальном аккаунте Gotech на Youtube.

      Комментарии (0)

        Let's block ads! (Why?)

        Детектор движения на основе биоинспирированного модуля OpenCV

        Занятие на вечер: ресайзинг элементов на pure JS

        пятница, 10 февраля 2017 г.

        Сравнение решений по балансировке высоконагруженных систем

        И вновь мы публикуем расшифровки выступлений с конференции HighLoad++, которая прошла в подмосковном Сколково 7—8 ноября 2016 года. Сегодня Евгений Пивень знакомит с решениями балансировки в облаках.

        Меня зовут Женя, я работаю в компании 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)

          Let's block ads! (Why?)

          [Из песочницы] Home Assistant или еще один «мозг» для проекта типа «Умный Дом»

          Добрый день, уважаемый читатель. На днях довелось мне поиграться с многим уже известной игрушкой от Google – Google Home. Штука хорошая — обзор ее я делать конечно не буду. В чулане совершенно случайно завалялись Raspberry PI 3 (RPi), Arduino Mega и еще им подобная мелочь, которую захотелось подключить к Google Home (GH) с целью голосового управления. Простого API у GH нет, но есть возможность с помощью стороннего сервиса организовать голосовое управление системой на RPi + Arduino с задержкой команд в несколько секунд.

          Читая буржуйские форумы (справедливости ради, нужно отметить, человек я повернутый на автоматизации и 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
          image

          Что позволяет сделать HASS с ресивером:

          Нам доступны: Источник, Громкость, Без Звука (при прослушивании аудио записей, доступны кнопки управления треками).

          Теперь же, более подробно рассмотрим yaml-ку. Я привожу несколько расширенную версию, на базе которой будет проще понять какие возможности у HASS есть, а также, возможно, поможет вам в настройке собственного окружения.

          configuration.yaml
          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

          И приступаем к заполнению.

          Так как у меня используется инкрементирование громкости каждую секунду — файл большой. Я приведу самые необходимые строки, остальное можно настроить по аналогии.

          Automation для Будильника
          - 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)

            Let's block ads! (Why?)