This commit is contained in:
svad05 2022-08-05 15:27:12 +03:00
commit b2a879978e
42 changed files with 4241 additions and 324 deletions

177
README.md
View File

@ -1,3 +1,178 @@
# profinet_io_dev
Драйвер profinet io device.
Драйвер 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);
```

View File

@ -1 +1 @@
sudo /usr/bin/gdb "$@"
sudo /usr/local/bin/gdb "$@"

131
libs/include/osal.h Normal file
View File

@ -0,0 +1,131 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_H
#define OSAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "sys/osal_sys.h"
#include "sys/osal_cc.h"
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef BIT
#define BIT(n) (1U << (n))
#endif
#ifndef NELEMENTS
#define NELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
#endif
#ifndef OS_WAIT_FOREVER
#define OS_WAIT_FOREVER 0xFFFFFFFF
#endif
#ifndef OS_MAIN
#define OS_MAIN int main
#endif
#ifndef OS_MUTEX
typedef void os_mutex_t;
#endif
#ifndef OS_SEM
typedef void os_sem_t;
#endif
#ifndef OS_THREAD
typedef void os_thread_t;
#endif
#ifndef OS_EVENT
typedef void os_event_t;
#endif
#ifndef OS_MBOX
typedef void os_mbox_t;
#endif
#ifndef OS_TIMER
typedef void os_timer_t;
#endif
void * os_malloc (size_t size);
void os_free (void * ptr);
void os_usleep (uint32_t us);
uint32_t os_get_current_time_us (void);
os_thread_t * os_thread_create (
const char * name,
uint32_t priority,
size_t stacksize,
void (*entry) (void * arg),
void * arg);
os_mutex_t * os_mutex_create (void);
void os_mutex_lock (os_mutex_t * mutex);
void os_mutex_unlock (os_mutex_t * mutex);
void os_mutex_destroy (os_mutex_t * mutex);
os_sem_t * os_sem_create (size_t count);
bool os_sem_wait (os_sem_t * sem, uint32_t time);
void os_sem_signal (os_sem_t * sem);
void os_sem_destroy (os_sem_t * sem);
os_event_t * os_event_create (void);
bool os_event_wait (
os_event_t * event,
uint32_t mask,
uint32_t * value,
uint32_t time);
void os_event_set (os_event_t * event, uint32_t value);
void os_event_clr (os_event_t * event, uint32_t value);
void os_event_destroy (os_event_t * event);
os_mbox_t * os_mbox_create (size_t size);
bool os_mbox_fetch (os_mbox_t * mbox, void ** msg, uint32_t time);
bool os_mbox_post (os_mbox_t * mbox, void * msg, uint32_t time);
void os_mbox_destroy (os_mbox_t * mbox);
os_timer_t * os_timer_create (
uint32_t us,
void (*fn) (os_timer_t * timer, void * arg),
void * arg,
bool oneshot);
void os_timer_set (os_timer_t * timer, uint32_t us);
void os_timer_start (os_timer_t * timer);
void os_timer_stop (os_timer_t * timer);
void os_timer_destroy (os_timer_t * timer);
#ifdef __cplusplus
}
#endif
#endif /* OSAL_H */

78
libs/include/osal_log.h Normal file
View File

@ -0,0 +1,78 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_LOG_H
#define OSAL_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "osal.h"
/* Log levels */
#define LOG_LEVEL_DEBUG 0x00
#define LOG_LEVEL_INFO 0x01
#define LOG_LEVEL_WARNING 0x02
#define LOG_LEVEL_ERROR 0x03
#define LOG_LEVEL_FATAL 0x04
#define LOG_LEVEL_MASK 0x07
#define LOG_LEVEL_GET(t) (t & LOG_LEVEL_MASK)
/* Log states */
#define LOG_STATE_ON 0x80
#define LOG_STATE_OFF 0x00
#define LOG_ENABLED(type) \
((LOG_LEVEL_GET (type) >= LOG_LEVEL) && (type & LOG_STATE_ON))
/** Log a message if it is enabled */
#define LOG(type, ...) \
do \
{ \
if (LOG_ENABLED (type)) \
{ \
os_log (type, __VA_ARGS__); \
} \
} while (0)
/** Log debug messages */
#define LOG_DEBUG(type, ...) LOG ((LOG_LEVEL_DEBUG | type), __VA_ARGS__)
/** Log informational messages */
#define LOG_INFO(type, ...) LOG ((LOG_LEVEL_INFO | type), __VA_ARGS__)
/** Log warning messages */
#define LOG_WARNING(type, ...) LOG ((LOG_LEVEL_WARNING | type), __VA_ARGS__)
/** Log error messages */
#define LOG_ERROR(type, ...) LOG ((LOG_LEVEL_ERROR | type), __VA_ARGS__)
/** Log fatal messages */
#define LOG_FATAL(type, ...) LOG ((LOG_LEVEL_FATAL | type), __VA_ARGS__)
#define LOG_DEBUG_ENABLED(type) LOG_ENABLED (LOG_LEVEL_DEBUG | type)
#define LOG_INFO_ENABLED(type) LOG_ENABLED (LOG_LEVEL_INFO | type)
#define LOG_WARNING_ENABLED(type) LOG_ENABLED (LOG_LEVEL_WARNING | type)
#define LOG_ERROR_ENABLED(type) LOG_ENABLED (LOG_LEVEL_ERROR | type)
#define LOG_FATAL_ENABLED(type) LOG_ENABLED (LOG_LEVEL_FATAL | type)
void os_log (uint8_t type, const char * fmt, ...) CC_FORMAT (2, 3);
#ifdef __cplusplus
}
#endif
#endif /* OSAL_LOG_H */

View File

@ -0,0 +1,54 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2021 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
/**
* @file
* @brief PNAL-specific configuration
*
* This file contains definitions of configuration settings for the
* PNAL layer.
*/
#ifndef PNAL_CONFIG_H
#define PNAL_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/**
* Thread priority and stack size
*/
typedef struct pnal_thread_cfg
{
uint32_t prio;
size_t stack_size;
} pnal_thread_cfg_t;
typedef struct pnal_cfg
{
pnal_thread_cfg_t snmp_thread;
pnal_thread_cfg_t eth_recv_thread;
pnal_thread_cfg_t bg_worker_thread;
} pnal_cfg_t;
#ifdef __cplusplus
}
#endif
#endif /* PNAL_CONFIG_H */

2257
libs/include/pnet_api.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
#ifndef PNET_EXPORT_H
#define PNET_EXPORT_H
#ifdef PNET_STATIC_DEFINE
# define PNET_EXPORT
# define PNET_NO_EXPORT
#else
# ifndef PNET_EXPORT
# ifdef profinet_EXPORTS
/* We are building this library */
# define PNET_EXPORT
# else
/* We are using this library */
# define PNET_EXPORT
# endif
# endif
# ifndef PNET_NO_EXPORT
# define PNET_NO_EXPORT
# endif
#endif
#ifndef PNET_DEPRECATED
# define PNET_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef PNET_DEPRECATED_EXPORT
# define PNET_DEPRECATED_EXPORT PNET_EXPORT PNET_DEPRECATED
#endif
#ifndef PNET_DEPRECATED_NO_EXPORT
# define PNET_DEPRECATED_NO_EXPORT PNET_NO_EXPORT PNET_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef PNET_NO_DEPRECATED
# define PNET_NO_DEPRECATED
# endif
#endif
#endif /* PNET_EXPORT_H */

View File

@ -0,0 +1,33 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2018 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
#ifndef PNET_OPTIONS_H
#define PNET_OPTIONS_H
#if !defined (PNET_MAX_PHYSICAL_PORTS)
/** Max number of physical ports */
#define PNET_MAX_PHYSICAL_PORTS 1
#endif
#if !defined (PNET_MAX_DIRECTORYPATH_SIZE)
/** Max directory path size, including termination */
#define PNET_MAX_DIRECTORYPATH_SIZE 240
#endif
#if !defined (PNET_OPTION_DRIVER_ENABLE)
#define PNET_OPTION_DRIVER_ENABLE 0
#endif
#endif /* PNET_OPTIONS_H */

View File

@ -0,0 +1,41 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2018 rt-labs AB, Sweden.
*
* This software is dual-licensed under GPLv3 and a commercial
* license. See the file LICENSE.md distributed with this software for
* full license information.
********************************************************************/
#ifndef PNET_VERSION_H
#define PNET_VERSION_H
/* #undef PROFINET_GIT_REVISION */
#if !defined(PNET_VERSION_BUILD) && defined(PROFINET_GIT_REVISION)
#define PNET_VERSION_BUILD PROFINET_GIT_REVISION
#endif
/* clang-format-off */
#define PNET_VERSION_MAJOR 0
#define PNET_VERSION_MINOR 2
#define PNET_VERSION_PATCH 0
#if defined(PNET_VERSION_BUILD)
#define PNET_VERSION \
"0.2.0+" PNET_VERSION_BUILD
#else
#define PNET_VERSION \
"0.2.0"
#endif
/* clang-format-on */
#endif /* PNET_VERSION_H */

View File

@ -0,0 +1,98 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef CC_H
#define CC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <assert.h>
#if defined(__clang__)
#if !defined(CLANG_ANALYZER_NORETURN)
#if __has_feature(attribute_analyzer_noreturn)
#define CLANG_ANALYZER_NORETURN __attribute__ ((analyzer_noreturn))
#else
#define CLANG_ANALYZER_NORETURN
#endif
#endif
#else
#define CLANG_ANALYZER_NORETURN
#endif
static inline void cc_assert (int exp) CLANG_ANALYZER_NORETURN
{
assert (exp); // LCOV_EXCL_LINE
}
#define CC_PACKED_BEGIN
#define CC_PACKED_END
#define CC_PACKED __attribute__ ((packed))
#define CC_FORMAT(str, arg) __attribute__ ((format (printf, str, arg)))
#if BYTE_ORDER == LITTLE_ENDIAN
#define CC_TO_LE16(x) ((uint16_t)(x))
#define CC_TO_LE32(x) ((uint32_t)(x))
#define CC_TO_LE64(x) ((uint64_t)(x))
#define CC_FROM_LE16(x) ((uint16_t)(x))
#define CC_FROM_LE32(x) ((uint32_t)(x))
#define CC_FROM_LE64(x) ((uint64_t)(x))
#define CC_TO_BE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_TO_BE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_TO_BE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_FROM_BE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_FROM_BE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_FROM_BE64(x) ((uint64_t)__builtin_bswap64 (x))
#else
#define CC_TO_LE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_TO_LE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_TO_LE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_FROM_LE16(x) ((uint16_t)__builtin_bswap16 (x))
#define CC_FROM_LE32(x) ((uint32_t)__builtin_bswap32 (x))
#define CC_FROM_LE64(x) ((uint64_t)__builtin_bswap64 (x))
#define CC_TO_BE16(x) ((uint16_t)(x))
#define CC_TO_BE32(x) ((uint32_t)(x))
#define CC_TO_BE64(x) ((uint64_t)(x))
#define CC_FROM_BE16(x) ((uint16_t)(x))
#define CC_FROM_BE32(x) ((uint32_t)(x))
#define CC_FROM_BE64(x) ((uint64_t)(x))
#endif
#define CC_ATOMIC_GET8(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET16(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET32(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_GET64(p) __atomic_load_n ((p), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET8(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET16(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET32(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ATOMIC_SET64(p, v) __atomic_store_n ((p), (v), __ATOMIC_SEQ_CST)
#define CC_ASSERT(exp) cc_assert (exp)
#ifdef __cplusplus
#define CC_STATIC_ASSERT(exp) static_assert (exp, "")
#else
#define CC_STATIC_ASSERT(exp) _Static_assert(exp, "")
#endif
#ifdef __cplusplus
}
#endif
#endif /* CC_H */

View File

@ -0,0 +1,77 @@
/*********************************************************************
* _ _ _
* _ __ | |_ _ | | __ _ | |__ ___
* | '__|| __|(_)| | / _` || '_ \ / __|
* | | | |_ _ | || (_| || |_) |\__ \
* |_| \__|(_)|_| \__,_||_.__/ |___/
*
* www.rt-labs.com
* Copyright 2017 rt-labs AB, Sweden.
*
* This software is licensed under the terms of the BSD 3-clause
* license. See the file LICENSE distributed with this software for
* full license information.
********************************************************************/
#ifndef OSAL_SYS_H
#define OSAL_SYS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <time.h>
#define OS_THREAD
#define OS_MUTEX
#define OS_SEM
#define OS_EVENT
#define OS_MBOX
#define OS_TIMER
typedef pthread_t os_thread_t;
typedef pthread_mutex_t os_mutex_t;
typedef struct os_sem
{
pthread_cond_t cond;
pthread_mutex_t mutex;
size_t count;
} os_sem_t;
typedef struct os_event
{
pthread_cond_t cond;
pthread_mutex_t mutex;
uint32_t flags;
} os_event_t;
typedef struct os_mbox
{
pthread_cond_t cond;
pthread_mutex_t mutex;
size_t r;
size_t w;
size_t count;
size_t size;
void * msg[];
} os_mbox_t;
typedef struct os_timer
{
timer_t timerid;
os_thread_t * thread;
pid_t thread_id;
bool exit;
void (*fn) (struct os_timer *, void * arg);
void * arg;
uint32_t us;
bool oneshot;
} os_timer_t;
#ifdef __cplusplus
}
#endif
#endif /* OSAL_SYS_H */

BIN
libs/lib/x86_64/libosal.a Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,34 @@
cmake_minimum_required (VERSION 3.0)
project (PN_ECHO_TEST VERSION 0.0.1)
set(CMAKE_CXX_STANDARD 20)
set(INC_DIRS ${INC_DIRS} ../../src/profinet)
set(INC_DIRS ${INC_DIRS} ../../src/interprocess)
set(INC_DIRS ${INC_DIRS} ../../src/shared_data)
set(SRC_FILES ${SRC_FILES} ../../src/shared_data/shared_data.cpp)
set(SRC_FILES ${SRC_FILES} ../../src/interprocess/profinet_shared_data.cpp)
set(SRC_FILES ${SRC_FILES} ./main.cpp)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
find_package(Boost 1.74.0)
if(Boost_FOUND)
set(INC_DIRS ${INC_DIRS} ${Boost_INCLUDE_DIRS})
else()
message(BOOST NOT FOUND)
endif()
message("Boost dir: " ${Boost_INCLUDE_DIRS})
message("Boost libs:" ${Boost_LIBRARIES})
add_executable(pn_echo_test ./main.cpp ${SRC_FILES})
target_include_directories(pn_echo_test PRIVATE
./
${INC_DIRS}
)

View File

@ -0,0 +1,143 @@
#include "profinet_shared_data_client.hpp"
#include <iostream>
#include <thread>
#define ECHO_MODULE_ID 0x00000040 /// Идентификатор тестового модуля
#define ECHO_SUBMOD_ID 0x00000140 /// Идентификатор тестового подмодуля
#define ECHO_INPUT_DATA_SIZE 8
#define ECHO_OUTPUT_DATA_SIZE 8
#define ECHO_PARAMETER_GAIN_IDX 125 /// Индекс параметра Gain для подмодуля ECHO
using namespace std;
uint32_t Echo_Gain = 0;
struct EchoData {
float data_f;
uint32_t data_i;
};
union EchoDataMem
{
EchoData data;
uint8_t mem[sizeof(EchoData)];
};
EchoDataMem Echo_inpCycData;
EchoDataMem Echo_outCycData;
uint8_t be_data_inp[sizeof(EchoData)];
void endian_convert_32(uint8_t * p_data)
{
uint8_t tmp = p_data[1];
p_data[1] = p_data[2];
p_data[2] = tmp;
tmp = p_data[0];
p_data[0] = p_data[3];
p_data[3] = tmp;
}
ProfinetSharedDataClient shared_data;
ProfinetData_Map * p_profinet_data;
uint32_t event_mask = ( ProfinetEvent::EVENT_CONNECTION_ESTABLISHED |
ProfinetEvent::EVENT_NEW_CYCLIC_DATA |
ProfinetEvent::EVENT_NEW_PARAM_DATA |
ProfinetEvent::EVENT_CONNECTION_ABORT );
int main(int argc, char * argv[])
{
p_profinet_data = shared_data.Connect("profinet_shared_data");
if (p_profinet_data == nullptr)
{
return 0;
}
/// Берем указатель на данные подмодуля в разделяемой памяти
auto echo_submod_ptr = shared_data.getSubmodule(ECHO_MODULE_ID, ECHO_SUBMOD_ID);
if (echo_submod_ptr->id != ECHO_SUBMOD_ID)
{
return 0;
}
/// Берем указатель на данные параметра подмодуля в разделяемолй памяти
auto echo_param_gain_ptr = shared_data.getSubmoduleParameter(ECHO_MODULE_ID, ECHO_SUBMOD_ID, ECHO_PARAMETER_GAIN_IDX);
if (echo_param_gain_ptr->index != ECHO_PARAMETER_GAIN_IDX)
{
return 0;
}
std::cout << "Strarting..." << std::endl;
for(;;)
{
uint32_t events = p_profinet_data->Events.wait_flags(event_mask);
if (events & ProfinetEvent::EVENT_CONNECTION_ESTABLISHED)
{
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_CONNECTION_ESTABLISHED);
std::cout << "Event: PLC connection established" << std::endl;
}
if (events & ProfinetEvent::EVENT_NEW_CYCLIC_DATA)
{
static uint32_t value_i = 0;
static float value_f = 0.0;
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_NEW_CYCLIC_DATA);
///Читаем данные от ПЛК
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);
if (value_i != Echo_outCycData.data.data_i)
{
value_i = Echo_outCycData.data.data_i;
std::cout << "New data_i from PLC: " << value_i << std::endl;
}
if (value_f != Echo_outCycData.data.data_f)
{
value_f = Echo_outCycData.data.data_f;
std::cout << "New data_f from PLC: " << value_f << std::endl;
}
///Подготавливаем данные для ПЛК
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);
}
if (events & ProfinetEvent::EVENT_NEW_PARAM_DATA)
{
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_NEW_PARAM_DATA);
std::cout << "Event: New parameter data: " << std::endl;
//Читаем параметр записанный ПЛК при установлении связи
echo_param_gain_ptr->data.Read(0, (uint8_t*)&Echo_Gain, echo_param_gain_ptr->length);
/// Конвертируем в литл эндиан
endian_convert_32((uint8_t*)&Echo_Gain);
std::cout << "Echo_Gain = " << std::to_string(Echo_Gain) << endl;
}
if (events & ProfinetEvent::EVENT_CONNECTION_ABORT)
{
p_profinet_data->Events.clear_flags(ProfinetEvent::EVENT_CONNECTION_ABORT);
std::cout << "Connection Aborted" << std::endl;
}
}
}

View File

@ -1,31 +1,40 @@
#********************************************************************
# _ _ _
# _ __ | |_ _ | | __ _ | |__ ___
# | '__|| __|(_)| | / _` || '_ \ / __|
# | | | |_ _ | || (_| || |_) |\__ \
# |_| \__|(_)|_| \__,_||_.__/ |___/
#
# http://www.rt-labs.com
# Copyright 2017 rt-labs AB, Sweden.
# See LICENSE file in the project root for full license information.
#*******************************************************************/
cmake_minimum_required (VERSION 3.14)
cmake_minimum_required (VERSION 3.0)
project (PN_DEV_TEST VERSION 0.0.1)
set(CMAKE_CXX_STANDARD 20)
set(LIBS_INSTALL_PATH ../libs)
set(PNET_PATH ../profinet_stack/p-net)
set(TARGET_NAME profinet_io_dev)
include(./utils/utils.cmake)
include(./profinet/profinet.cmake)
include(./nlohmann_json/nlohmann_json.cmake)
include(./file_api/file_api.cmake)
include(./configuration/configuration.cmake)
include (./interprocess/interprocess.cmake)
include (./shared_data/shared_data.cmake)
add_executable(pn_dev_test ./main.cpp ${SRC_FILES})
set(SRC_FILES ${SRC_FILES} ./app.cpp)
target_include_directories(pn_dev_test PRIVATE
# Копирование заглушки скрипта установки параметров сети.
# Если не копировать, то в недрах pnet после fork вызовется exit и
# вызовутся деструкторы объектов, что может привести к непредвиденным последствиям.
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/set_network_parameters
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
#Копирование файла конфигурации
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/program_configure.json
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
#Копирование тестового файла конфигурации устройства Profinet
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/profinet_device_configure.json
DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_executable(${TARGET_NAME} ./main.cpp ${SRC_FILES})
target_include_directories(${TARGET_NAME} PRIVATE
./
${INC_DIRS}
${LIBS_INSTALL_PATH}/include
@ -35,6 +44,6 @@ target_include_directories(pn_dev_test PRIVATE
${PNET_PATH}/src/ports/linux
)
target_link_directories(pn_dev_test PUBLIC ${LIBS_INSTALL_PATH}/lib)
target_link_directories(${TARGET_NAME} PUBLIC ${LIBS_INSTALL_PATH}/lib/x86_64)
target_link_libraries (pn_dev_test PUBLIC profinet osal)
target_link_libraries (${TARGET_NAME} PUBLIC profinet osal ${Boost_LIBRARIES})

45
src/app.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "app.hpp"
#include "program_config.hpp"
bool App::Init(std::string profinet_config_file)
{
ProfinetSettings profinet_settings;
ProfinetDeviceSettings profinet_dev_settings;
/// Читаем настройки из файла
if (!programconf_getProfinetSettings("program_configure.json", profinet_settings))
{
return false;
}
///Создаем структуру в разделяемой памяти
ProfinetData_Map * p_profinetMap = shared_data_.Create("profinet_shared_data");
/// Читаем настройки из файла, выделяем память под структуру модулей и подмодулей
if (!programconf_getProfinetDeviceSettings(profinet_settings.profinet_device_config, profinet_dev_settings, p_profinetMap, shared_data_))
{
return false;
}
/// Настройка Profinet: Инициализация pnet, добавление DAP слотов и подслотов и подключение к ним модулей и подмодулей,
if (!profinet_.Config(profinet_settings, profinet_dev_settings, p_profinetMap))
{
return false;
}
/**
* Циклические данные устройства передаются с определенным периодом, сообщения от контроллера при этом не требуются.
*/
/// Запуск потока Profinet
profinet_.Start();
return true;
}
void App::Run()
{
for (;;)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}

14
src/app.hpp Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "./profinet/profinet.hpp"
#include "./interprocess/profinet_shared_data.hpp"
#include <string>
class App {
public:
bool Init(std::string profinet_config_file);
void Run();
private:
ProfinetSharedData shared_data_;
Profinet profinet_;
};

View File

@ -1,8 +0,0 @@
#pragma once
#include <string>
struct AppSettings
{
std::string EthDevName; /// Имя адаптера
};

View File

@ -7,6 +7,12 @@
#include <cstdint>
#include "../profinet/profinet_data_map.hpp"
#include <boost/interprocess/managed_shared_memory.hpp>
using namespace boost::interprocess;
using json = nlohmann::json;
using string = std::string;
@ -131,93 +137,228 @@ static bool programconf_getPnetDeviceSettings(json& j, ProfinetDeviceSettings& p
return true;
}
static pnet_submodule_dir_t programconf_dataDirConvert(string dir_str)
static uint8_t programconf_dataDirConvert(string dir_str)
{
pnet_submodule_dir_t out = PNET_DIR_NO_IO;
ProfinetSubmoduleDir out = PROFINET_SUBMOD_DIR_NO_IO;
if (dir_str == "PNET_DIR_IO")
{
out = PNET_DIR_IO;
out = PROFINET_SUBMOD_DIR_IO;
}
else if (dir_str == "PNET_DIR_INPUT")
{
out = PNET_DIR_INPUT;
out = PROFINET_SUBMOD_DIR_INPUT;
}
else if (dir_str == "PNET_DIR_OUTPUT")
{
out = PNET_DIR_OUTPUT;
out = PROFINET_SUBMOD_DIR_OUTPUT;
}
return out;
}
static bool programconf_getProfinetModulesSettings(json& j, std::vector<std::shared_ptr<ProfinetModule>>& modules_vect)
{
/**
* @brief Считаем общий размер данных требуемый всеми подмодулями: их циклическими данными и параметрами
* Пробегает по файлу конфигурации и считает размер требуемой памяти.
* @param j
* @param memory_size
* @return true
* @return false
*/
static bool programconf_calc_memory_size(json& j, uint32_t& memory_size)
{
if (!j.contains("ProfinetDeviceModulesSettings"))
return false;
auto& modules_sett = j["ProfinetDeviceModulesSettings"]["modules"];
memory_size = sizeof(ProfinetData_Map) + modules_sett.size() * sizeof(ProfinetData_Module);
for (auto& mod_set : modules_sett)
{
auto& submodules_sett = mod_set["submodules"];
memory_size += submodules_sett.size() * sizeof(ProfinetData_Submodule);
for (auto& submod_sett : submodules_sett)
{
uint16_t in_data_len = static_cast<uint16_t>(submod_sett["cyc_inp_data_size"].get<int>());
uint16_t out_data_len = static_cast<uint16_t>(submod_sett["cyc_out_data_size"].get<int>());
memory_size+= in_data_len + out_data_len;
///Параметры подмодуля
auto& params_sett = submod_sett["parameters"];
memory_size += params_sett.size() * sizeof(ProfinetData_Parameter);
/// Записываем параметры в подмодуль
for (auto& parm_sett : params_sett)
{
uint16_t data_size = static_cast<uint16_t>(parm_sett["data_size"].get<int>());
memory_size += data_size;
}
}
}
return true;
}
static bool programconf_getProfinetModulesSettings(json& j, ProfinetData_Map * p_data_map, ProfinetSharedData& shared_data)
{
if (!j.contains("ProfinetDeviceModulesSettings"))
return false;
uint32_t required_mem_size;
if (!programconf_calc_memory_size(j, required_mem_size))
return false;
/// Проверяем достаточно ли у нас выделено памяти под текущую конфигурацию
/// Если недостаточно, то выделяем недостающее.
shared_data.checkMemorySize(required_mem_size);
/// Далее идет выделение
auto& modules_sett = j["ProfinetDeviceModulesSettings"]["modules"];
///Узнаем количество модулей
uint32_t mudules_number = modules_sett.size();
if (mudules_number == 0)
{
return false;
}
///Выделяем разделяемую память
p_data_map->p_mods = shared_data.allocateData<ProfinetData_Module>(mudules_number);
if (p_data_map->p_mods == nullptr)
{
return false;
}
p_data_map->mods_nbr = mudules_number;
uint32_t module_index = 0;
for (auto& mod_set : modules_sett)
{
uint32_t mod_id = hexstring_to_int(mod_set["id"].get<string>());
string mod_name = mod_set["name"].get<string>();
///Модуль
auto& mod = p_data_map->p_mods[module_index];
auto module_ptr = ProfinetModule::Create(mod_id, mod_name);
if (module_ptr == nullptr)
++module_index;
/// Копируем параметры модуля
mod.id = mod_id;
mod.name = mod_set["name"].get<string>();
auto& submodules_sett = mod_set["submodules"];
uint32_t submudules_number = submodules_sett.size();
if (submudules_number == 0)
continue;
///Выделяем разделяемую память
mod.p_submods = shared_data.allocateData<ProfinetData_Submodule>(submudules_number);
if (mod.p_submods == nullptr)
{
return false;
}
auto& submodules_sett = mod_set["submodules"];
mod.submods_nbr = submudules_number;
uint32_t submodules_index = 0;
for (auto& submod_sett : submodules_sett)
{
uint32_t submod_id = hexstring_to_int(submod_sett["id"].get<string>());
string submod_name = submod_sett["name"].get<string>();
pnet_data_cfg_t data_cfg;
data_cfg.data_dir = programconf_dataDirConvert(submod_sett["cyc_data_dir"].get<string>());
data_cfg.insize = submod_sett["cyc_inp_data_size"].get<int>();
data_cfg.outsize = submod_sett["cyc_out_data_size"].get<int>();
auto submodule_ptr = ProfinetSubmodule::Create(submod_id, submod_name, data_cfg);
if (submodule_ptr == nullptr)
auto& submod = mod.p_submods[submodules_index];
++submodules_index;
/// Копируем данные подмодуля
submod.id = submod_id;
submod.name = submod_sett["name"].get<string>();
submod.cyc_data_dir = programconf_dataDirConvert(submod_sett["cyc_data_dir"].get<string>());
submod.cyc_indata_len = static_cast<uint16_t>(submod_sett["cyc_inp_data_size"].get<int>());
submod.cyc_outdata_len = static_cast<uint16_t>(submod_sett["cyc_out_data_size"].get<int>());
/// Выделяем память под циклические данные
if (submod.cyc_indata_len)
{
return false;
}
auto& params_sett = submod_sett["parameters"];
/// Записываем параметры в подмодуль
for (auto& parm_sett : params_sett)
{
uint32_t index = parm_sett["index"].get<int>();
string parm_name = parm_sett["name"].get<string>();
uint16_t length = parm_sett["data_size"].get<int>();
auto submod_param_ptr = ProfinetParameter::Create(index, parm_name, length);
if (submod_param_ptr == nullptr)
uint8_t * p_data = shared_data.allocateData<uint8_t>(submod.cyc_indata_len);
if (p_data == nullptr)
{
return false;
}
submodule_ptr->addParameter(submod_param_ptr);
submod.inp_data.Init(p_data, submod.cyc_indata_len);
}
module_ptr->addSubmodule(submodule_ptr);
}
modules_vect.push_back(module_ptr);
if (submod.cyc_outdata_len)
{
uint8_t * p_data = shared_data.allocateData<uint8_t>(submod.cyc_outdata_len);
if (p_data == nullptr)
{
return false;
}
submod.out_data.Init(p_data, submod.cyc_outdata_len);
}
///Параметры подмодуля
auto& params_sett = submod_sett["parameters"];
uint32_t params_nbr = params_sett.size();
///Выделяем разделяемую память
submod.p_params = shared_data.allocateData<ProfinetData_Parameter>(params_nbr);
if (submod.p_params == nullptr)
{
return false;
}
submod.params_nbr = params_nbr;
uint32_t params_index = 0;
/// Записываем параметры в подмодуль
for (auto& parm_sett : params_sett)
{
auto& param = submod.p_params[params_index];
++params_index;
param.index = static_cast<uint32_t>(parm_sett["index"].get<int>());
param.name = parm_sett["name"].get<string>();
param.length = static_cast<uint16_t>(parm_sett["data_size"].get<int>());
if (param.length == 0)
{
return false;
}
/// Выделяем память для параметров
uint8_t * p_data = shared_data.allocateData<uint8_t>(param.length);
if (p_data == nullptr)
{
return false;
}
param.data.Init(p_data, param.length);
}
}
}
return true;
}
bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDeviceSettings& pn_sett, std::vector<std::shared_ptr<ProfinetModule>>& modules_vect)
bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDeviceSettings& pn_sett,
ProfinetData_Map * p_data_map, ProfinetSharedData& shared_data)
{
json j;
@ -231,7 +372,7 @@ bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDevice
return false;
}
if (!programconf_getProfinetModulesSettings(j, modules_vect))
if (!programconf_getProfinetModulesSettings(j, p_data_map, shared_data))
{
return false;
}
@ -239,3 +380,4 @@ bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDevice
return true;
}

View File

@ -5,9 +5,10 @@
#include "json.hpp"
#include "profinet_settings.hpp"
#include "profinet_module.hpp"
#include "profinet_data_map.hpp"
#include "profinet_shared_data.hpp"
bool programconf_getProfinetSettings(std::string file_path, ProfinetSettings& pn_sett);
bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDeviceSettings& pn_sett,
std::vector<std::shared_ptr<ProfinetModule>>& modules_vect);
bool programconf_getProfinetDeviceSettings(std::string file_path, ProfinetDeviceSettings& pn_sett,
ProfinetData_Map * p_data_map, ProfinetSharedData& shared_data);

View File

@ -2,33 +2,67 @@
#include <mutex>
#include <cstdint>
#include <condition_variable>
class Flags {
#include <iostream>
#include "flags_iface.hpp"
class Flags : public FlagsIface {
public:
Flags() : flags_{0} {}
void set_flag(uint32_t mask) {
std::lock_guard<std::mutex> guard(mutex_);
flags_ |= mask;
Flags() : flags_{0} {
}
uint32_t get_flag(uint32_t mask) {
std::lock_guard<std::mutex> guard(mutex_);
return flags_ & mask;
virtual void set_flag(uint32_t mask) override
{
{
std::lock_guard<std::mutex> guard(mutex_);
flags_ |= mask;
}
flag_cond_.notify_all();
}
uint32_t get_flags() {
std::lock_guard<std::mutex> guard(mutex_);
return flags_;
virtual uint32_t get_flag(uint32_t mask) override {
uint32_t out;
{
std::lock_guard<std::mutex> guard(mutex_);
out = flags_ & mask;
}
return out;
}
void clear_flags(uint32_t mask)
virtual uint32_t get_flags() override {
uint32_t out;
{
std::lock_guard<std::mutex> guard(mutex_);
out = flags_;
}
return out;
}
virtual void clear_flags(uint32_t mask) override
{
std::lock_guard<std::mutex> guard(mutex_);
flags_ &= ~mask;
}
/**
* @brief Данная реализация блочит потоки pnet.
* Нужно разбираться.
*
*/
virtual uint32_t wait_flags(uint32_t mask) override
{
uint32_t out;
std::unique_lock<std::mutex> lk(mutex_);
flag_cond_.wait(lk, [this, mask]{return static_cast<bool>(flags_ & mask);});
out = flags_ & mask;
lk.unlock();
return out;
}
private:
uint32_t flags_;
mutable std::mutex mutex_;
std::mutex mutex_;
std::condition_variable flag_cond_;
};

19
src/flags/flags_iface.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
class FlagsIface {
public:
virtual void set_flag(uint32_t mask) = 0;
virtual uint32_t get_flag(uint32_t mask) = 0;
virtual uint32_t get_flags() = 0;
virtual void clear_flags(uint32_t mask) = 0;
virtual uint32_t wait_flags(uint32_t mask) = 0;
virtual ~FlagsIface() {}
};

View File

@ -0,0 +1,67 @@
#pragma once
#include <cstdint>
#include <mutex>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include "flags_iface.hpp"
class SharedFlags : public FlagsIface {
public:
SharedFlags() : flags_{0} {
}
virtual void set_flag(uint32_t mask) override {
{
std::lock_guard guard(mutex_);
flags_ |= mask;
}
/// Уведомляем об изменении флага
flag_cond_.notify_all();
}
virtual uint32_t get_flag(uint32_t mask) override {
uint32_t out;
{
std::lock_guard guard(mutex_);
out = flags_ & mask;
}
return out;
}
virtual uint32_t get_flags() override{
uint32_t out;
{
std::lock_guard guard(mutex_);
out = flags_;
}
return out;
}
virtual void clear_flags(uint32_t mask) override
{
std::lock_guard guard(mutex_);
flags_ &= ~mask;
}
virtual uint32_t wait_flags(uint32_t mask) override
{
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(mutex_wait_);
flag_cond_.wait(lock); //Ожидаем изменения данных
/// Данные изменились
uint32_t out;
{
std::lock_guard guard(mutex_);
out = flags_ & mask;
}
return out;
}
private:
uint32_t flags_;
boost::interprocess::interprocess_mutex mutex_;
boost::interprocess::interprocess_mutex mutex_wait_;
boost::interprocess::interprocess_condition flag_cond_;
};

View File

@ -0,0 +1,17 @@
set(INC_DIRS ${INC_DIRS} ./interprocess)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
find_package(Boost 1.74.0)
if(Boost_FOUND)
set(INC_DIRS ${INC_DIRS} ${Boost_INCLUDE_DIRS})
else()
message(BOOST NOT FOUND)
endif()
message("Boost dir: " ${Boost_INCLUDE_DIRS})
message("Boost libs:" ${Boost_LIBRARIES})
set(SRC_FILES ${SRC_FILES} ./interprocess/profinet_shared_data.cpp)

View File

@ -0,0 +1,65 @@
#include "../interprocess/profinet_shared_data.hpp"
#include <cstring>
using namespace boost::interprocess;
/**
* @brief Расчет размера данных для выделения памяти
*
* @param data_settings
* @return uint32_t
*/
static uint32_t calc_mem_size(ProfinetData_Map& data_settings)
{
uint32_t size = 0;
for (int i = 0; i < data_settings.mods_nbr; ++i)
{
auto& submods = data_settings.p_mods[i].p_submods;
for (int j = 0; j < data_settings.p_mods[i].submods_nbr; ++j)
{
auto& submod = submods[j];
size+= submod.cyc_indata_len + submod.cyc_outdata_len;
auto& params = submod.p_params;
for (int k = 0; k < submod.params_nbr; ++k)
{
size+= params[i].length;
}
}
}
return size + sizeof(ProfinetData_Map);
}
ProfinetData_Map * ProfinetSharedData::Create(std::string mem_name)
{
/// Если память уже выделена, удаляем
shared_memory_object::remove(mem_name.c_str());
/// Для начала выделяем 65к байт. Потом вызывается функция checkMemorySize и размер моджет быть увеличен
shmem_ = managed_shared_memory{create_only, mem_name.c_str(), 65536};
mem_name_ = mem_name;
///Создаем данные в разделяемой памяти
p_profinet_data_ = shmem_.construct<ProfinetData_Map>("ProfinetData_Map")();
return p_profinet_data_;
}
void ProfinetSharedData::checkMemorySize(uint32_t required_mem_size)
{
uint32_t free_size = shmem_.get_size();
if (required_mem_size < free_size)
return;
/// Увеличиваем память
managed_shared_memory::grow(mem_name_.c_str(), required_mem_size - free_size);
/// Заново открываем память(может измениться указатель)
shmem_ = managed_shared_memory{open_only, mem_name_.c_str()};
/// Заново находим или создаем структуру ProfinetData_Map
p_profinet_data_ = shmem_.find_or_construct<ProfinetData_Map>("ProfinetData_Map")();
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <string>
#include <mutex>
#include "../profinet/profinet_data_map.hpp"
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
class ProfinetSharedData {
public:
~ProfinetSharedData() {
///Удаляем память
boost::interprocess::shared_memory_object::remove(mem_name_.c_str());
//std::cout << "Destruct: ProfinetSharedData: Shared data deleted" << std::endl;
}
/**
* @brief Выделяыет раздеяемую память под структуру ProfinetData_Map
*
* @param mem_name
* @return ProfinetData_Map*
*/
ProfinetData_Map * Create(std::string mem_name);
template<class T>
T * allocateData(uint32_t nbr) {
return shmem_.construct<T>(boost::interprocess::anonymous_instance)[nbr]();
}
/**
* @brief Проверяет, что размер свободной памяти не меньше чем значение required_mem_size
* Если меньше, то увеличиват память.
*
* @param required_mem_size
* @return uint32_t
*/
void checkMemorySize(uint32_t required_mem_size);
ProfinetData_Map * p_profinet_data_;
private:
boost::interprocess::managed_shared_memory shmem_;
std::string mem_name_;
};

View File

@ -0,0 +1,113 @@
#pragma once
#include "../profinet/profinet_data_map.hpp"
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>
#include <map>
#include <memory>
class ProfinetSharedDataClient {
public:
/**
* @brief Подключается к выделенной разделяемой памяти
*
* @param mem_name
*/
ProfinetData_Map * Connect(std::string mem_name) {
ProfinetData_Map * out{nullptr};
try
{
shmem_ = boost::interprocess::managed_shared_memory{boost::interprocess::open_only, mem_name.c_str()};
try
{
auto mem = shmem_.find<ProfinetData_Map>("ProfinetData_Map");
out = mem.first;
///Упорядочиваем память для удобства обращения по индентификатору Profinet
collected_data = collect(out);
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
return nullptr;
}
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
return out;
}
return out;
}
/**
* @brief Возвращает указатель на данные подмодуля в разделяемой памяти
*
* @param module_id идентификатор модуля
* @param submodule_id идентификатор подмодуля
* @return ProfinetData_Submodule*
*/
ProfinetData_Submodule * getSubmodule(uint32_t module_id, uint32_t submodule_id)
{
return (*collected_data)[module_id].submods[submodule_id].p_submod;
}
/**
* @brief Возвращает указатель на данные параметра подмодуля в разделяемой памяти
*
* @param module_id идентификатор модуля
* @param submodule_id идентификатор подмодуля
* @param param_index индекс параметра
* @return ProfinetData_Parameter*
*/
ProfinetData_Parameter * getSubmoduleParameter(uint32_t module_id, uint32_t submodule_id, uint32_t param_index)
{
return (*collected_data)[module_id].submods[submodule_id].params[param_index];
}
private:
struct ProfinetSharedDataSubmodule {
ProfinetData_Submodule* p_submod;
std::map<uint32_t, ProfinetData_Parameter*> params;
};
struct ProfinetSharedDataModule {
ProfinetData_Module* p_mod;
std::map<uint32_t, ProfinetSharedDataSubmodule> submods;
};
/**
* @brief Собирает данные в словарь, можно использовать для удобства обращения к данным по идентификатору
*
* @param p_data_map
* @return std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>>
*/
static std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> collect(ProfinetData_Map * p_data_map) {
std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> p_map(new std::map<uint32_t, ProfinetSharedDataModule>);
for (int i = 0; i < p_data_map->mods_nbr; ++i)
{
auto& mod = (*p_map)[p_data_map->p_mods[i].id];
mod.p_mod = &p_data_map->p_mods[i];
for (int j = 0; j < p_data_map->p_mods[i].submods_nbr; ++j)
{
auto& submod = mod.submods[p_data_map->p_mods[i].p_submods[j].id];
submod.p_submod = &p_data_map->p_mods[i].p_submods[j];
for (int k = 0; k < p_data_map->p_mods[i].p_submods[j].params_nbr; ++k)
{
submod.params[p_data_map->p_mods[i].p_submods[j].p_params[k].index] = &p_data_map->p_mods[i].p_submods[j].p_params[k];
}
}
}
return p_map;
}
private:
boost::interprocess::managed_shared_memory shmem_;
std::shared_ptr<std::map<uint32_t, ProfinetSharedDataModule>> collected_data; /// Упорядоченная структура указателей на разделяемую память
};

View File

@ -1,99 +1,20 @@
#include "app_settings.hpp"
#include "../libs/include/pnet_api.h"
#include "profinet.hpp"
#include <thread>
#include <iostream>
#include "program_config.hpp"
#include "app.hpp"
#define ECHO_MODULE_ID 0x00000040 /// Идентификатор тестового модуля
#define ECHO_SUBMOD_ID 0x00000140 /// Идентификатор тестового подмодуля
#define ECHO_INPUT_DATA_SIZE 8
#define ECHO_OUTPUT_DATA_SIZE 8
#define ECHO_PARAMETER_GAIN_IDX 125 /// Индекс параметра Gain для подмодуля ECHO
#include "./interprocess/profinet_shared_data.hpp"
using namespace std;
uint32_t Echo_Gain = 0;
struct EchoData {
float data_f;
uint32_t data_i;
};
union EchoDataMem
{
EchoData data;
uint8_t mem[sizeof(EchoData)];
};
EchoDataMem Echo_inpCycData;
EchoDataMem Echo_outCycData;
uint8_t be_data_inp[sizeof(EchoData)];
void endian_convert_32(uint8_t * p_data)
{
uint8_t tmp = p_data[1];
p_data[1] = p_data[2];
p_data[2] = tmp;
tmp = p_data[0];
p_data[0] = p_data[3];
p_data[3] = tmp;
}
App app;
int main(int argc, char * argv[])
{
Echo_inpCycData.data.data_f = 54321.9f; /// Преобразовать в big endian
Echo_inpCycData.data.data_i = 876; /// Преобразовать в big endian
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_f);
endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_i);
Echo_outCycData.data.data_f = 0.0f;
Echo_outCycData.data.data_i = 0;
ProfinetSettings profinet_settings;
ProfinetDeviceSettings profinet_dev_settings;
/// Читаем настройки из файла
if (!programconf_getProfinetSettings("program_configure.json", profinet_settings))
{
return 0;
}
std::vector<std::shared_ptr<ProfinetModule>> Modules;
/// Читаем настройки из файла
if (!programconf_getProfinetDeviceSettings(profinet_settings.profinet_device_config, profinet_dev_settings, Modules))
{
return 0;
}
Profinet profinet;
/// Настройка Profinet: Инициализация pnet, добавление DAP слотов и подслотов и подключение к ним модулей и подмодулей,
if (!profinet.Config(profinet_settings, profinet_dev_settings, Modules))
{
return 0;
}
app.Init("program_configure.json");
/**
* Циклические данные устройства передаются с определенным периодом, сообщения от контроллера при этом не требуются.
*/
/// Запуск потока Profinet
profinet.Start();
auto echo_mod = profinet.getModule(ECHO_MODULE_ID);
auto echo_submod = echo_mod->getSubmodulePtr(ECHO_SUBMOD_ID);
echo_submod->putDataToPlc(Echo_inpCycData.mem);
uint32_t ix = 0;
while(1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
++ix;
std::cout << "tick: " << ix << endl;
}
app.Run();
return 0;
}

View File

@ -1,3 +1,7 @@
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
message("log enable")
add_definitions(-DLOG_ENABLE)
endif()
set(SRC_FILES ${SRC_FILES}
./profinet/profinet.cpp

View File

@ -33,7 +33,6 @@ using namespace std;
#define GET_HIGH_BYTE(id) ((id >> 8) & 0xFF)
#define GET_LOW_BYTE(id) (id & 0xFF)
#define APP_MAIN_SLEEPTIME_US 5000 * 1000
#define APP_SNMP_THREAD_PRIORITY 1
#define APP_SNMP_THREAD_STACKSIZE 256 * 1024 /* bytes */
#define APP_ETH_THREAD_PRIORITY 10
@ -43,9 +42,7 @@ using namespace std;
#define FLAGS_AREP_APP_READY 0x00000001
//#define RELEASE
#ifndef RELEASE
#ifdef LOG_ENABLE
#define LOG(STR) log_.put(STR)
#else
#define LOG(STR)
@ -166,9 +163,38 @@ Profinet::Profinet() : m_pnet_data({nullptr, 0, UINT32_MAX, 0}),
}
void Profinet::createInternalStucture(ProfinetData_Map * const p_data_map)
{
for (int i = 0; i < p_data_map->mods_nbr; ++i)
{
auto& mod = p_data_map->p_mods[i];
auto module_ptr = ProfinetModule::Create(mod.id, mod.name);
for (int j = 0; j < mod.submods_nbr; ++j)
{
auto& submod = mod.p_submods[j];
pnet_data_cfg_t dap_data_cfg = { static_cast<pnet_submodule_dir_t>(submod.cyc_data_dir),
submod.cyc_indata_len, submod.cyc_outdata_len };
auto submodule_ptr = ProfinetSubmodule::Create(submod.id, submod.name, dap_data_cfg, &submod.inp_data, &submod.out_data);
for (int k = 0; k < submod.params_nbr; ++k)
{
auto& param = submod.p_params[k];
auto Parameter = ProfinetParameter::Create(param.index, param.name, &param.data);
submodule_ptr->addParameter(Parameter);
}
module_ptr->addSubmodule(submodule_ptr);
}
addModule(module_ptr);
}
}
bool Profinet::Config(ProfinetSettings& Settings,
ProfinetDeviceSettings& DevSettings,
std::vector<std::shared_ptr<ProfinetModule>>& Modules)
ProfinetData_Map * const p_data_map)
{
bool ret = true;
pnet_cfg_t pnet_cfg = {0};
@ -206,6 +232,8 @@ bool Profinet::Config(ProfinetSettings& Settings,
/// Инициализация библиотеки pnet
m_pnet_data.pnet_ptr = pnet_init (&pnet_cfg);
p_events_ = &p_data_map->Events;
if (m_pnet_data.pnet_ptr == nullptr)
return false;
@ -214,15 +242,17 @@ bool Profinet::Config(ProfinetSettings& Settings,
/// Создаем обязательный модуль DAP
auto dap_module = ProfinetModule::Create(PNET_MOD_DAP_IDENT, "DAP 1");
/// Создаем обязательный подмодуль DAP Identity 1
auto dap_submodule_indent_1 = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_IDENT, "DAP Identity 1", dap_data_cfg);
auto dap_submodule_indent_1 = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_IDENT, "DAP Identity 1", dap_data_cfg, nullptr, nullptr);
/// Создаем обязательный подмодуль DAP IFACE1 IDENT
auto dap_submodule_iface_1_indent_1 = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_INTERFACE_1_IDENT, "DAP IFACE1 IDENT", dap_data_cfg);
auto dap_submodule_iface_1_indent_1 = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_INTERFACE_1_IDENT, "DAP IFACE1 IDENT", dap_data_cfg, nullptr, nullptr);
/// Создаем обязательный подмодуль DAP Port 1
auto dap_subslot_iface_1_port1_ident = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT, "DAP Port 1", dap_data_cfg);
auto dap_subslot_iface_1_port1_ident = ProfinetSubmodule::Create(PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT, "DAP Port 1", dap_data_cfg, nullptr, nullptr);
/// Добавляем подмодули к модулю
ret&= dap_module->addSubmodule(dap_submodule_indent_1);
ret&= dap_module->addSubmodule(dap_submodule_iface_1_indent_1);
ret&= dap_module->addSubmodule(dap_subslot_iface_1_port1_ident);
/// Добавляем модуль в конфигурацию
addModule(dap_module);
/// Создаем слот для модуля DAP и доключаем к нему модуль dap_module
ret&= addSlotAndPlugModule(PNET_SLOT_DAP_IDENT, dap_module);
@ -234,21 +264,8 @@ bool Profinet::Config(ProfinetSettings& Settings,
/// 3. Подмодуль "DAP Port 1"
ret&= addSubslotAndPlugSubmodule(PNET_SLOT_DAP_IDENT, PNET_SUBSLOT_DAP_INTERFACE_1_PORT_1_IDENT, dap_subslot_iface_1_port1_ident);
///Копируем конфигурацию модулей
for (auto module_ptr : Modules)
{
if (module_ptr == nullptr)
{
ret = false;
break;
}
if (!addModule(module_ptr))
{
ret = false;
break;
}
}
///Создаем внутреннию структуру модулей
createInternalStucture(p_data_map);
return ret;
}
@ -500,6 +517,7 @@ void Profinet::initDataAndIoxs()
{
}
submodule_ptr->inputSetData( m_pnet_data.pnet_ptr,
m_pnet_data.api,
slot_ptr->m_slot_nbr,
@ -543,13 +561,6 @@ void Profinet::PeriodicOperations()
/// Ждем время в мкс: ticks_period_us, по умолчанию 1мс
std::this_thread::sleep_for(std::chrono::microseconds(m_run_period_us));
if (flags_.get_flag(FLAGS_AREP_APP_READY))
{
pnet_application_ready (m_pnet_data.pnet_ptr, m_pnet_data.arep_for_appl_ready);
flags_.clear_flags(FLAGS_AREP_APP_READY);
LOG("FLAGS_AREP_APP_READY");
}
/// Вызов cyclic io
if (++m_cyclic_io_cnt >= m_cyclic_io_period_ms)
{
@ -559,11 +570,25 @@ void Profinet::PeriodicOperations()
/// запускаем pnet
pnet_handle_periodic (m_pnet_data.pnet_ptr);
/// Нужно делать после pnet_handle_periodic(следующий тик).
if (flags_.get_flag(FLAGS_AREP_APP_READY))
{
pnet_application_ready (m_pnet_data.pnet_ptr, m_pnet_data.arep_for_appl_ready);
flags_.clear_flags(FLAGS_AREP_APP_READY);
LOG("PeriodicOperations: FLAGS_AREP_APP_READY");
}
}
}
void Profinet::cyclicIoData()
{
///Циклический обмен только когда установлено соединение
if (!isConnectedToController())
{
return;
}
for (auto slot : m_slots)
{
shared_ptr<ProfinetSlot>& slot_ptr = slot.second;
@ -586,22 +611,23 @@ void Profinet::cyclicIoData()
if (submodule_ptr->m_data_cfg.outsize > 0)
{
bool outdata_updated;
uint16_t outdata_length = submodule_ptr->m_data_cfg.outsize;
uint8_t outdata_iops;
bool data_updated;
/// Копируем данные от контроллера в m_out_data_ptr
submodule_ptr->outputGetData( m_pnet_data.pnet_ptr,
m_pnet_data.api,
slot_ptr->m_slot_nbr,
subslot_ptr->m_subslot_nbr);
}
subslot_ptr->m_subslot_nbr, data_updated);
if (data_updated)
{
p_events_->set_flag(EVENT_NEW_CYCLIC_DATA);
}
}
if (submodule_ptr->m_data_cfg.insize > 0)
{
uint8_t indata_iocs;
uint8_t indata_iops = PNET_IOXS_GOOD;
/**
* @brief Отправляем данные подмодуля в ПЛК
*
@ -661,25 +687,32 @@ int Profinet::callbackStateInd ( uint32_t arep, pnet_event_values_t event)
if (event == PNET_EVENT_ABORT)
{
LOG("callbackStateInd:PNET_EVENT_ABORT");
LOG("callbackStateInd: PNET_EVENT_ABORT: The AR has been closed.");
if (pnet_get_ar_error_codes (m_pnet_data.pnet_ptr, arep, &err_cls, &err_code) == 0)
{
LOG("err_cls= " + std::to_string(err_cls));
LOG("err_code= " + std::to_string(err_code));
/**
* @brief Коды ошибок в err_cls и err_code
* TODO: Нужно передавать в управляющее приложение
*/
}
else
{
/// Ошибок нет
}
p_events_->set_flag(EVENT_CONNECTION_ABORT);
m_pnet_data.arep = UINT32_MAX;
/* Only abort AR with correct session key */
///os_event_set (app->main_events, APP_EVENT_ABORT);
}
else if (event == PNET_EVENT_PRMEND)
{
LOG("callbackStateInd:PNET_EVENT_PRMEND");
LOG("callbackStateInd: PNET_EVENT_PRMEND: Controller has sent all write records.");
if (isConnectedToController())
{
/// Если уже были подключены и пришел запрос на еще одно подключение
@ -706,14 +739,21 @@ int Profinet::callbackStateInd ( uint32_t arep, pnet_event_values_t event)
the internal stack states */
m_pnet_data.arep_for_appl_ready = arep;
flags_.set_flag(FLAGS_AREP_APP_READY);
///os_event_set (app->main_events, APP_EVENT_READY_FOR_DATA); ///
}
else if (event == PNET_EVENT_DATA)
{
LOG("callbackStateInd:PNET_EVENT_DATA");
LOG("callbackStateInd: PNET_EVENT_DATA: The cyclic data transmission is now properly set up");
/// Стартовал обмен cyclic io
}
else if (event == PNET_EVENT_STARTUP)
{
LOG("callbackStateInd: PNET_EVENT_STARTUP: A connection has been initiated.");
p_events_->set_flag(EVENT_CONNECTION_ESTABLISHED);
}
else if (event == PNET_EVENT_APPLRDY)
{
LOG("callbackStateInd: PNET_EVENT_APPLRDY: Controller has acknowledged the APPL_RDY message.");
}
return 0;
}
@ -759,10 +799,7 @@ int Profinet::callbackDcontrolInd ( uint32_t arep,
int Profinet::callbackCcontrolInd ( uint32_t arep, pnet_result_t * p_result)
{
LOG("callbackCcontrolInd");
/**
* @brief
* Можно оставить пустым
*/
return 0;
}
@ -800,11 +837,11 @@ int Profinet::callbackReadInd ( uint32_t arep,
///3. Передаем данные параметра
/**
* По идее тут нужно утановить мьютекс lock, а unlock сделать потом, когда данные будет причитаны стеком pnet
* Проблема в том, что нет ни одного события которое бы сообщало об окончании процесса чтения данных стеком pnet.
* Нужно поисследовать.
* В функции getReadBufPtr данные копируются во временный буфер и уже затем передаются в стек pnet.
* Это нужно из-за отсутствия события которое бы сообщало об окончании процесса чтения данных стеком pnet.
*
*/
*pp_read_data = param_ptr->getDataPointer();
*pp_read_data = param_ptr->getReadBufPtr();
*p_read_length = param_ptr->length;
return 0;
@ -844,7 +881,13 @@ int Profinet::callbackWriteInd ( uint32_t arep,
}
///3. Копируем данные
param_ptr->writeParameter(p_write_data);
if (!param_ptr->writeParameter(p_write_data))
{
return -1;
}
/// Устанавливаем событие
p_events_->set_flag(EVENT_NEW_PARAM_DATA);
return 0;
}

View File

@ -15,14 +15,27 @@
#include "../log/log.hpp"
#include "../flags/flags.hpp"
#include "../flags/flags_iface.hpp"
#include "profinet_data_map.hpp"
class Profinet {
public:
enum Events
{
EVENT_CONNECTION_ESTABLISHED = 0x00000001, /// Установлено соединение с ПЛК
EVENT_NEW_CYCLIC_DATA = 0x00000002, /// Получение новых циклических данных от ПЛК
EVENT_NEW_PARAM_DATA = 0x00000004, /// Новая запись параметра от ПЛК
EVENT_CONNECTION_ABORT = 0x00000008
};
Profinet();
~Profinet();
bool Config(ProfinetSettings& Settings,
ProfinetDeviceSettings& DevSettings,
std::vector<std::shared_ptr<ProfinetModule>>& Modules);
ProfinetData_Map * const p_data_map);
/**
* @brief Добавляет ранее созданный слот slot_ptr
@ -143,8 +156,10 @@ public:
*/
void Start();
~Profinet();
uint32_t EventWait(uint32_t mask) { return p_events_->wait_flags(mask); }
void EventClear(uint32_t mask) { p_events_->clear_flags(mask); }
private:
ProfinetServiceData m_pnet_data;
@ -168,28 +183,43 @@ private:
std::map<uint32_t, std::shared_ptr<ProfinetModule>> m_modules;
Log log_;
Flags flags_;
Flags flags_; /// Внутренние флаги: взаимодействиe между потоком Profinet и потоком стека Pnet
FlagsIface * p_events_; /// Флаги для внешних событий. Это более общие флаги, например флаг получения данных от ПЛК, а в каком подмодуле, уже не уточняется.
private:
/**
* @brief Проверяет наличие слота slot_nbr в конфигурации
* @brief Проверяет наличие слота slot_nbr в конфигурации и возвращает указатель на него
*
* @param slot_nbr номер слота
* @return ProfinetSlot* - указатель на слот, при успехе или nullptr есои слот в конфигурации отсутсвует
* @return std::shared_ptr<ProfinetSlot> указатель на слот, при успехе или nullptr если слот в конфигурации отсутсвует
*/
std::shared_ptr<ProfinetSlot> getSlotPtr(uint16_t slot_nbr);
/**
* @brief Проверяет наличие подслота в слоте и возвращает указатель на него
*
* @param slot_nbr номер слота
* @param subslot_nbr номер подслота
* @return std::shared_ptr<ProfinetSubslot> указатель на подслот, при успехе или nullptr если подслот в конфигурации отсутсвует
*/
std::shared_ptr<ProfinetSubslot> getSubslotPtr(uint16_t slot_nbr, uint16_t subslot_nbr);
/**
* @brief Флаг подключение к контроллеру(ПЛК).
* Реализация взята с примера стека pnet. На самом деле не понятна роль AREP.
* Открытой информации недостаточно, нужно покупать и смотреть спецификацию. *
*
* @return true подключение есть
* @return false
*/
bool isConnectedToController() { return m_pnet_data.arep != UINT32_MAX; };
void initDataAndIoxs();
void cyclicIoData();
void createInternalStucture(ProfinetData_Map * const p_data_map);
public:
/**
*

View File

@ -0,0 +1,58 @@
#pragma once
#include <cstdint>
#include <string>
#include "../flags/shared_flags.hpp"
#include "../shared_data/shared_data.hpp"
enum ProfinetSubmoduleDir
{
PROFINET_SUBMOD_DIR_NO_IO = 0x00,
PROFINET_SUBMOD_DIR_INPUT = 0x01,
PROFINET_SUBMOD_DIR_OUTPUT = 0x02,
PROFINET_SUBMOD_DIR_IO = 0x03
};
enum ProfinetEvent
{
EVENT_CONNECTION_ESTABLISHED = 0x00000001, /// Установлено соединение с ПЛК
EVENT_NEW_CYCLIC_DATA = 0x00000002, /// Получение новых циклических данных от ПЛК
EVENT_NEW_PARAM_DATA = 0x00000004, /// Новая запись параметра от ПЛК
EVENT_CONNECTION_ABORT = 0x00000008 /// Разрыв соединения
};
struct ProfinetData_Parameter {
uint32_t index; /// Идентфикатор параметра
std::string name; /// Наименование
uint16_t length; /// длина параметра в байтах
SharedData data; /// Указатель на данные параметра
};
struct ProfinetData_Submodule{
uint32_t id; /// Идентификатор подмодуля
std::string name; /// Наименование
uint8_t cyc_data_dir; /// Направление данных
uint16_t cyc_indata_len; /// Длина входных для ПЛК данных
uint16_t cyc_outdata_len; /// Длина выходных для ПЛК данных
SharedData inp_data; /// Входные данные(данные от контроллера к устройству)
SharedData out_data; /// Выходные данные(данные от устройства к контроллеру)
ProfinetData_Parameter * p_params; /// Параметры подмодуля
uint32_t params_nbr; /// Количество параметров
};
struct ProfinetData_Module {
uint32_t id;
std::string name;
ProfinetData_Submodule * p_submods; /// Подмодули
uint32_t submods_nbr; /// Количество подмодулей
};
struct ProfinetData_Map {
SharedFlags Events; /// События profinet
ProfinetData_Module * p_mods; /// Модули
uint32_t mods_nbr; /// Количество модулей
};

View File

@ -1,50 +1,58 @@
#include "profinet_parameter.hpp"
#include <cstring>
ProfinetParameter::ProfinetParameter(uint32_t Index, std::string Name, uint16_t Length) :
ProfinetParameter::ProfinetParameter(uint32_t Index, std::string Name, SharedDataIface * p_data) :
index(Index),
name(Name),
length(Length),
data_ptr_(new uint8_t[Length])
length(p_data->Size()),
p_data_(p_data),
read_buf_(nullptr)
{
if (length != 0)
{
read_buf_ = std::shared_ptr<uint8_t[]>(new uint8_t[length]);
}
};
bool ProfinetParameter::readParameter(uint8_t * const data_ptr)
{
std::lock_guard<std::mutex> guard(data_mutex_); /// Синхронизация чтения и записи
if (data_ptr == nullptr)
if ((data_ptr == nullptr) || (p_data_ == nullptr))
{
return false;
}
memcpy(data_ptr, data_ptr_.get(), length);
p_data_->Read(0, (void*)data_ptr, length);
return true;
}
bool ProfinetParameter::writeParameter(const uint8_t * data_ptr)
{
std::lock_guard<std::mutex> guard(data_mutex_); /// Синхронизация чтения и записи
if (data_ptr == nullptr)
if ((data_ptr == nullptr) || (p_data_ == nullptr))
{
return false;
}
memcpy(data_ptr_.get(), data_ptr, length);
p_data_->Write(0, (void*)data_ptr, length);
return true;
}
uint8_t * ProfinetParameter::getDataPointer()
uint8_t * ProfinetParameter::getReadBufPtr()
{
/**
* @brief Слабое утешение от ситуации когда приложение записывает данные в параметр, а контроллер хочет этот параметр
* @brief Cитуации когда приложение записывает данные в параметр, а контроллер хочет этот параметр
* прочитать и вызвался коллбэк profinet_cb_read. В этом случае будем тут висеть пока приложение не запишет параметр.
*
*/
std::lock_guard<std::mutex> guard(data_mutex_);
return data_ptr_.get();
if (p_data_ == nullptr)
return nullptr;
uint8_t * p_read_buf = read_buf_.get();
/// Копируем данные в буфер
p_data_->Read(0, p_read_buf, length);
return p_read_buf;
}

View File

@ -1,20 +1,26 @@
#pragma once
#include <memory>
#include <cstdint>
#include <string>
#include <memory>
#include <mutex>
#include "../shared_data/shared_data_iface.hpp"
class ProfinetParameter
{
public:
static std::shared_ptr<ProfinetParameter> Create(uint32_t Index, std::string Name, uint16_t Length)
static std::shared_ptr<ProfinetParameter> Create(uint32_t Index, std::string Name, SharedDataIface * p_data)
{
return std::shared_ptr<ProfinetParameter>(new ProfinetParameter(Index, Name, Length));
if (p_data == nullptr)
{
return nullptr;
}
return std::shared_ptr<ProfinetParameter>(new ProfinetParameter(Index, Name, p_data));
}
ProfinetParameter(uint32_t Index, std::string Name, uint16_t Length);
ProfinetParameter(uint32_t Index, std::string Name, SharedDataIface * p_data);
/**
* @brief Защищенная запись данных параметра
@ -39,17 +45,18 @@ public:
* Для этого ему в колбэке нужно предоставить указатель на данные параметра и длину данных.
* Подразумевается, что эта функция будет использоваться только внутри колбэка стека pnet.
* см. profinet_cb_read.cpp
* Не использовать эту функцию в приложении!!!
* @return uint8_t*
*/
uint8_t * getDataPointer();
uint8_t * getReadBufPtr();
private:
public:
const std::string name; /// Имя параметра
const uint32_t index; /// Идентфикатор параметра
const uint16_t length; /// длина параметра в байтах
private:
std::shared_ptr<uint8_t[]> data_ptr_; /// указатель на данные параметра
mutable std::mutex data_mutex_; /// Доступ к данным
};
std::shared_ptr<uint8_t[]> read_buf_; ///Буфер данных для передачи его в стек pnet для операции чтения
SharedDataIface * const p_data_; ///Указатель на данные параметра(в разделяемой области)
};

View File

@ -3,24 +3,34 @@
ProfinetSubmodule::ProfinetSubmodule(uint32_t submodule_id,
std::string submodule_name,
pnet_data_cfg_t& submodule_data_cfg) :
pnet_data_cfg_t& submodule_data_cfg,
SharedDataIface * inp_data_ptr,
SharedDataIface * out_data_ptr) :
m_id(submodule_id),
m_name(submodule_name),
m_data_cfg(submodule_data_cfg),
inp_data_ptr_(nullptr),
out_data_ptr_(nullptr),
inp_data_ptr_(inp_data_ptr),
out_data_ptr_(out_data_ptr),
m_indata_iocs(0),
m_outdata_iops(0)
m_outdata_iops(0),
data_updated_(false),
out_data_buf_(nullptr),
inp_data_buf_(nullptr)
{
/// Могут быть подмодули без данных, например DAP
if (m_data_cfg.insize > 0)
if (m_data_cfg.outsize && (out_data_ptr_!=nullptr))
{
inp_data_ptr_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.insize]);
if (m_data_cfg.outsize == out_data_ptr_->Size())
{
out_data_buf_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.outsize]);
}
}
if (m_data_cfg.outsize > 0)
if (m_data_cfg.insize && (inp_data_ptr_ != nullptr))
{
out_data_ptr_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.outsize]);
if (m_data_cfg.insize == inp_data_ptr_->Size())
{
inp_data_buf_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.insize]);
}
}
}
@ -47,18 +57,30 @@ bool ProfinetSubmodule::inputSetData( pnet_t * pnet_ptr,
uint16_t subslot_nbr,
uint8_t iops)
{
std::lock_guard<std::mutex> guard(inp_data_mutex_);
if ((pnet_ptr == nullptr) || (inp_data_ptr_ == nullptr))
if ((pnet_ptr == nullptr) ||
((inp_data_buf_ == nullptr) && (m_data_cfg.insize > 0)))
{
return false;
}
/**
* @brief Вызов когда m_data_cfg.insize==0 и inp_data_ptr_== nullptr
* нужен для корректного установления соединения
*
*/
///Копируем данные в буфер
if (inp_data_ptr_ != nullptr)
{
inp_data_ptr_->Read(0, inp_data_buf_.get(), m_data_cfg.insize);
}
/// Копируем данные для передачи в ПЛК
pnet_input_set_data_and_iops( pnet_ptr,
api,
slot_nbr,
subslot_nbr,
inp_data_ptr_.get(),
inp_data_buf_.get(),
m_data_cfg.insize,
iops);
@ -68,69 +90,56 @@ bool ProfinetSubmodule::inputSetData( pnet_t * pnet_ptr,
bool ProfinetSubmodule::outputGetData( pnet_t * pnet_ptr,
uint32_t api,
uint16_t slot_nbr,
uint16_t subslot_nbr )
uint16_t subslot_nbr, bool& data_update )
{
bool out = true;
/// Существуют подмодули без данных, например DAP
if ((pnet_ptr == nullptr) || (out_data_ptr_ == nullptr))
if ((pnet_ptr == nullptr) || ((out_data_ptr_ == nullptr) && (m_data_cfg.outsize > 0)))
{
return false;
}
std::lock_guard<std::mutex> guard(out_data_mutex_);
uint16_t outdata_length = m_data_cfg.outsize;
uint8_t outdata_iops;
bool updated;
/// Копируем данные от полученные от контроллера
/**
* @brief Флаг data_updated_ устанавливается внутри pnet_output_get_data_and_iops почему-то с задержкой 1 сек.
* Поэтому условием новых данных является не равность нулю outdata_length.
*/
/// Копируем данные полученные от контроллера
pnet_output_get_data_and_iops (pnet_ptr,
api,
slot_nbr,
subslot_nbr,
&updated,
out_data_ptr_.get(),
&data_updated_,
out_data_buf_.get(),
&outdata_length,
&outdata_iops);
///out_data_ptr_ данные из буфера
///Когда новых данных нет, то outdata_length == 0
if ((out_data_ptr_ != nullptr) && (outdata_length == m_data_cfg.outsize))
{
out_data_ptr_->Write(0, out_data_buf_.get(), m_data_cfg.outsize);
data_update = true;
}
else
{
data_update = false;
}
m_outdata_iops = outdata_iops;
data_updated_ = updated;
if (m_data_cfg.outsize != outdata_length)
{
/// Неправильная длина данных
out = false;
}
else if (outdata_iops == PNET_IOXS_BAD)
{
/// Что-то не так с данными от контроллера
out = false;
}
return true;
}
bool ProfinetSubmodule::getDataFromPlc(uint8_t * data_ptr)
{
std::lock_guard<std::mutex> guard(out_data_mutex_);
if ((data_ptr == nullptr) || (out_data_ptr_ == nullptr))
{
return false;
}
memcpy(data_ptr, out_data_ptr_.get(), m_data_cfg.outsize);
return true;
}
bool ProfinetSubmodule::putDataToPlc(uint8_t * data_ptr)
{
std::lock_guard<std::mutex> guard(inp_data_mutex_);
if ((data_ptr == nullptr) || (inp_data_ptr_ == nullptr))
{
return false;
}
memcpy(inp_data_ptr_.get(), data_ptr, m_data_cfg.insize);
return true;
}
return out;
}

View File

@ -7,6 +7,7 @@
#include <mutex>
#include "profinet_parameter.hpp"
#include "../shared_data/shared_data_iface.hpp"
#include "../../libs/include/pnet_api.h"
@ -15,21 +16,27 @@ class ProfinetSubmodule {
public:
static std::shared_ptr<ProfinetSubmodule> Create(uint32_t submodule_id,
std::string submodule_name,
pnet_data_cfg_t& submodule_data_cfg)
pnet_data_cfg_t& submodule_data_cfg,
SharedDataIface * inp_data_ptr,
SharedDataIface * out_data_ptr)
{
return std::shared_ptr<ProfinetSubmodule>(new ProfinetSubmodule(submodule_id,
submodule_name,
submodule_data_cfg));
submodule_data_cfg,
inp_data_ptr,
out_data_ptr));
}
ProfinetSubmodule(uint32_t submodule_id,
std::string submodule_name,
pnet_data_cfg_t& submodule_data_cfg);
pnet_data_cfg_t& submodule_data_cfg,
SharedDataIface * inp_data_ptr,
SharedDataIface * out_data_ptr);
bool addParameter(std::shared_ptr<ProfinetParameter>& param);
std::shared_ptr<ProfinetParameter> getParameterPtr(uint32_t index);
bool inputSetData( pnet_t * pnet_ptr,
uint32_t api,
uint16_t slot_nbr,
@ -39,13 +46,11 @@ public:
bool outputGetData(pnet_t * pnet_ptr,
uint32_t api,
uint16_t slot_nbr,
uint16_t subslot_nbr);
uint16_t subslot_nbr, bool& data_update);
bool getDataFromPlc(uint8_t * data_ptr);
bool putDataToPlc (uint8_t * data_ptr);
bool isDataUpdated() { return data_updated_; };
bool isDataUpdated() {
return data_updated_;
};
public:
const uint32_t m_id; /// Идентификатор подмодуля
@ -62,12 +67,13 @@ public:
private:
/// Набор параметров подмодуля
std::map<uint32_t, std::shared_ptr<ProfinetParameter>> m_params;
/// Буферы для взаимодействия с pnet и решения вопросов синхронизации
std::shared_ptr<uint8_t[]> out_data_buf_;
std::shared_ptr<uint8_t[]> inp_data_buf_;
std::shared_ptr<uint8_t[]> inp_data_ptr_; /// Входные циклические данные (DEV->PLC)
std::mutex inp_data_mutex_;
std::shared_ptr<uint8_t[]> out_data_ptr_; /// Выходные циклические данные (PLC->DEV)
std::mutex out_data_mutex_;
SharedDataIface * const inp_data_ptr_; /// Входные циклические данные (DEV->PLC)
SharedDataIface * const out_data_ptr_; /// Выходные циклические данные (PLC->DEV)
bool data_updated_;
};

16
src/set_network_parameters Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env sh
############################################
# Скрипт для установки параметров сети #
############################################
#
# Данный скрипт запускается стеком pnet при инициализации.
# Если файл с именем set_network_parameters будет отсутсвовать,
# то в недрах pnet после вызова fork будет вызван exit, что может привести
# к нежелательным последствиям, например вызовам деструкторов копий объектов
# родительского процесса.
#
echo "set_network_parameters: This is dummy script wich called by pnet stack. As example see pnet/src/ports/linux/set_network_parameters."
exit 0

View File

@ -0,0 +1,2 @@
set(SRC_FILES ${SRC_FILES} ./shared_data/shared_data.cpp)
set(INC_DIRS ${INC_DIRS} ./shared_data)

View File

@ -0,0 +1,46 @@
#include "shared_data.hpp"
#include <iostream>
#include <cstring>
bool SharedData::Init(uint8_t * p_data, uint32_t size)
{
if ((p_data == nullptr) || (size == 0))
{
return false;
}
p_data_ = p_data;
size_ = size;
std::lock_guard guard(mutex_);
std::memset(p_data_, 0, size);
return true;
}
bool SharedData::Write(uint32_t offset_bytes, void * p_data, uint32_t length)
{
if ((p_data == nullptr) || ((offset_bytes + length) > size_))
{
return false;
}
std::lock_guard guard(mutex_);
std::memcpy(p_data_ + offset_bytes, p_data, length);
return true;
}
bool SharedData::Read(uint32_t offset_bytes, void * p_data, uint32_t length)
{
if ((p_data == nullptr) || ((offset_bytes + length) > size_))
{
return false;
}
std::lock_guard guard(mutex_);
std::memcpy(p_data, p_data_ + offset_bytes, length);
return true;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <mutex>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include "shared_data_iface.hpp"
class SharedData : public SharedDataIface {
public:
SharedData() : p_data_{nullptr}, size_{0} {};
virtual bool Init(uint8_t * p_data, uint32_t size) override;
virtual bool Write(uint32_t offset_bytes, void * p_data, uint32_t length) override;
virtual bool Read(uint32_t offset_bytes, void * p_data, uint32_t length) override;
virtual uint32_t Size() const override { return size_; }
private:
boost::interprocess::interprocess_mutex mutex_;
uint8_t * p_data_;
uint32_t size_;
};

View File

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
class SharedDataIface {
public:
virtual bool Init(uint8_t * p_data, uint32_t size) = 0;
virtual bool Write(uint32_t offset_bytes, void * p_data, uint32_t length) = 0;
virtual bool Read(uint32_t offset_bytes, void * p_data, uint32_t length) = 0;
virtual uint32_t Size() const = 0;
virtual ~SharedDataIface() {}
};