# profinet_io_dev

Драйвер profinet io device. Предназначен для работы в фоне и взаимодействием с Profinet контроллером с использованием ethernet.

Реализует следующий функционал:

1.  Читает из json файла структуру данных Profinet,
2.  Конфигурирует и запускает циклический обмен по Profinet,
3.  Принимает циклические данные от контроллера Profinet и записывает их в разделяемую память.
4.  Берет циклические данные из разделяемой памяти и передает их контроллеру Profinet

Использует следующие библиотеки:
1. pnet
2. boost (1.74)

## Структура проекта:
* ***debug_support*** - содержит скрипт для запуска отладки с правами суперпользователя и обновления gdb (нужно для Ubuntu 22.04, там есть баг с gdb - он падает при переключении на другой поток в отлажеваемом ПО),
* ***libs*** - собранные библиотеки ***pnet*** и используемая ей ***osal***,
* ***profinet_stack*** - исходники библиотеки pnet,
* ***profinet_test*** - тестовые проекты. Наиболее интересен проект в папке ***sample_app_echo*** реализует функционал тестового модуля ECHO через взаимодействие с profinet_io_dev и подключение к разделяемой памяти.
* ***src*** - исходный код приложения profinet_io_dev.

## Общее описание данных Profinet
Терминалогия принятая в  Profinet:
*   Выходные данные - данные от контроллера к устройству,
*   Входные данные - данные от устройства к контроллеру.

Профинет предоставляет абстракцию типичной системы управления на базе ПЛК, который имеет универсальные слоты в которые подключаются специализированные модули, например CAN, RS-422, аналоговых входов и т.д. В терминах Profinet все тоже самое: есть контроллер у которого есть слоты в которые он при установлении связи подключает модули устройства.

На стороне контроллера имеется один или несколько слотов(slots), каждый из которых имеет один или несколько подслотов(subslots).

На стороне устройства имеется один или несколько модулей(modules), каждый из которых имеет один или несколько подмодулей(submodules).

К каждому слоту на стороне контроллера может быть поключен модуль со стороны устройства.

К каждому подслоту на стороне контроллера может быть подключен подмодуль со стороны устройства.

Каждый подмодуль может включать:
*   Выходные циклические данные (от контроллера к устройству)
*   Входные циклические данные (от устройства к контроллеру)
*   один или больше параметров.

Могут быть подмодули не содержащие данных вообще.

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

Параметры могут использоваться устройством для расчета входных данных для контроллера.

Описание конфигурации данных Profinet определяется специальным ***gsdml*** файлом, который на самом деле является ***xml*** файлом содержимое которого описано в спецификации Profinet. Этот файл нужен для всяких СКАДА систем при конфигурировании контроллера Profinet, загружаешь в СКАДА этот файл и в нем появляются Profinet устройство описанное в этом файле, а так-же все его модули, подмодули и параметры.

Идентификация модулей и подмодулей осуществляется с помощью 32-х битного идентификатора.
Идентификация параметра осуществляется с помощью 32-х битного индекса.

Установление связи выглядит следующим образом:
1. Мастер запрашивает соединение,
2. Дает команду устройству: модуль с идентификатором A подключить к слоту с номером B.
3. Дает команду устройству: подмодуль с идентификатором A входящий в состав модуля с идентификатором B подключить к подслоту с номером C слота с номером D.
4. Записывает параметры подключенного подмодуля.
5. Начинается обмен циклическими данными.

**Все данные в Profinet пишутся в порядке big endian.**


## Циклический обмен

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

## Файл конфигурации *program_configure.json*

Описывает общие настройки ПО.

***ProfinetSettings:***
*   *ticks_us* - период таймера в мкс который вызывает pnet и реализует логику обмена.
*   *cyclic_ms* - период обмена циклическими данными.
*   *eth_dev_name* - наименование устройства ethernet в Linux через которое будет идти обмен по Profinet.
*   *profinet_device_config* - путь к json файлу конфигурации устройства, который описывает структуру модулей и подмодулей.

## Файл конфигурации устройства Profinet

Описывает структуру модулей и подмодулей. В качетсве примера можно рассмотреть описание тестового модуля ECHO: profinet_device_configure.json.

***ProfinetDeviceSettings***

Содержить часть параметров структуры ***pnet_cfg*** из ***pnet_api.h***. Это стандартные параметры Profinet которые указаны и описаны в спецификации. Например имя станции или имя устройства.

***ProfinetDeviceModulesSettings***

Описание структуры данных устройства.
* ***modules***
    *   *id* - идентификатор модуля в шеснадцатиричном или десятичном виде. Шеснадцатиричный вид обозначется как в С через 0х.
    *   *name* - имя модуля, в обмене не используется, нужно для удобства.
    *   ***submodules*** - описание подмодулей:
        * *id* - идентификатор подмодуля,
        * *name* - имя подмодуля. В обмене не используется, нужно для удобства.
        * *cyc_data_dir* - направление циклических данных. Может принимать значения:
            * PNET_DIR_NO_IO - нет циклических данных,
            * PNET_DIR_IO - входные и выходные циклические данные,
            * PNET_DIR_INPUT - только входные циклические данные,
            * PNET_DIR_OUTPUT - только выходные циклические данные.
        *  *cyc_inp_data_size* - размер входных циклических данных в байтах,
        * *cyc_out_data_size* - размер выходных циклических данных в байтах,
        * ***parameters*** - описание параметров:
            *  *index* - индекс параметра,
            *  *name* - наименование параметра. В обмене не используется, нужно для удобства.
            * *data_size* - размер данных параметра в байтах.

## Межпроцессное взаимодействие

### Разделяемая память

При запуске ПО выделяет блок разделяемой памяти с наименованием ***"profinet_shared_data"***.

Структура описана в ***profinet_data_map.hpp***.

API для работы с памятью приведено в файле: ***profinet_shared_data_client.hpp***

### Пример подключения к памяти:

```C++
ProfinetSharedDataClient shared_data;

ProfinetData_Map * p_profinet_data = shared_data.Connect("profinet_shared_data");

/// Берем указатель на данные подмодуля в разделяемой памяти
auto echo_submod_ptr = shared_data.getSubmodule(ECHO_MODULE_ID, ECHO_SUBMOD_ID);
    
/// Берем указатель на данные параметра подмодуля в разделяемолй памяти
auto echo_param_gain_ptr = shared_data.getSubmoduleParameter(ECHO_MODULE_ID, ECHO_SUBMOD_ID, ECHO_PARAMETER_GAIN_IDX);

```
### Пример ожидания наступления события
В структуре `ProfinetData_Map` есть разделяемые флаги `Events` типа `SharedFlags` которые могут быть использованы для ожидания следующих событий:
* `EVENT_CONNECTION_ESTABLISHED` - соединение установлено
* `EVENT_NEW_CYCLIC_DATA` - получены новые циклические данные. Не указывает на то, что данные изменились, просто по профинету была получена новая порция цикличесиких данных от контроллера.
* `EVENT_NEW_PARAM_DATA` - контроллером была осуществлена запись данных параметров какого-либо подмодуля. 
* `EVENT_CONNECTION_ABORT` - соединение с контроллером разорвано.

Пример ожидания установления соединения или приема новых циклических данных:
```C++
uint32_t events = p_profinet_data->Events.wait_flags(EVENT_CONNECTION_ESTABLISHED | EVENT_NEW_CYCLIC_DATA);

```
`wait_flags` - блокирует поток до момента установления флага.

Когда флаг установлен, его нужно сбросить:
```C++
p_profinet_data->Events.clear_flags(EVENT_NEW_CYCLIC_DATA);
```

### Пример чтения данных параметров подмодуля

```C++
uint32_t Echo_Gain = 0;
echo_param_gain_ptr->data.Read(0, (uint8_t*)&Echo_Gain, echo_param_gain_ptr->length);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_Gain);
```

### Пример чтения и записи циклических данных подмодуля
```C++
///Читаем данные от ПЛК
echo_submod_ptr->out_data.Read(0, Echo_outCycData.mem, echo_submod_ptr->cyc_outdata_len);

/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_outCycData.data.data_f);

///Подготавливаем данные для ПЛК
Echo_inpCycData.data.data_i = Echo_Gain * Echo_outCycData.data.data_i;
Echo_inpCycData.data.data_f = Echo_Gain * Echo_outCycData.data.data_f;
///Конвертируем в биг эндиан
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_i);
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_f);
///Отправляем данные для ПЛК
echo_submod_ptr->inp_data.Write(0, Echo_inpCycData.mem, echo_submod_ptr->cyc_indata_len);
```

### pipe каналы

Обмен происходит по двум каналам, по умолчанию это:
* profinet_io_dev_out
* profinet_io_dev_inp

Путь к аналам задается в конфигурационном файле `program_configure.json`.

ПО сначала открывает канал для чтения(inp), затем ожидает подключения клиента, после чего открывает канал для записи(out).

Обмен происходит с помощью json rpc, протокол описан в файле  `src/interprocess/pipes/json_rpc_protocol.txt`.




