1. Добавил скрипт build.sh - собирает библиотеку pnet и группирует получившиеся файлы в папку install, туда же помещает библиотеку osal. Теперь все библиотеки и их заголовки в одном месте, а не распизаны по папкам в папке build. 2. Добавил проект sample_app в папку profinet_test. Он отвязан от процесса сборки pnet и использует уже собранную библиотеку из папки install.
520 lines
16 KiB
C
520 lines
16 KiB
C
/*********************************************************************
|
|
* _ _ _
|
|
* _ __ | |_ _ | | __ _ | |__ ___
|
|
* | '__|| __|(_)| | / _` || '_ \ / __|
|
|
* | | | |_ _ | || (_| || |_) |\__ \
|
|
* |_| \__|(_)|_| \__,_||_.__/ |___/
|
|
*
|
|
* 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.
|
|
********************************************************************/
|
|
|
|
#define _GNU_SOURCE /* For asprintf() */
|
|
|
|
#include "sampleapp_common.h"
|
|
#include "app_gsdml.h"
|
|
#include "app_log.h"
|
|
#include "app_utils.h"
|
|
|
|
#include "osal.h"
|
|
#include "osal_log.h" /* For LOG_LEVEL */
|
|
#include "pnal.h"
|
|
#include "pnal_filetools.h"
|
|
#include <pnet_api.h>
|
|
|
|
#include <net/if.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sampleapp_main.h"
|
|
|
|
#if PNET_MAX_PHYSICAL_PORTS == 1
|
|
#define APP_DEFAULT_ETHERNET_INTERFACE "eth0"
|
|
#else
|
|
#define APP_DEFAULT_ETHERNET_INTERFACE "br0,eth0,eth1"
|
|
#endif
|
|
|
|
#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
|
|
#define APP_ETH_THREAD_STACKSIZE 4096 /* bytes */
|
|
#define APP_BG_WORKER_THREAD_PRIORITY 5
|
|
#define APP_BG_WORKER_THREAD_STACKSIZE 4096 /* bytes */
|
|
|
|
/* Note that this sample application uses os_timer_create() for the timer
|
|
that controls the ticks. It is implemented in OSAL, and the Linux
|
|
implementation uses a thread internally. To modify the timer thread priority,
|
|
modify OSAL or use some other timer */
|
|
|
|
app_args_t app_args = {0};
|
|
|
|
/************************* Utilities ******************************************/
|
|
|
|
void show_usage()
|
|
{
|
|
printf ("\nSample application for p-net Profinet device stack.\n");
|
|
printf ("\n");
|
|
printf ("Wait for connection from IO-controller.\n");
|
|
printf ("Then read buttons (input) and send to controller.\n");
|
|
printf ("Listen for application LED output (from controller) and set "
|
|
"application LED state.\n");
|
|
printf ("It will also send a counter value (useful also without buttons and "
|
|
"LED).\n");
|
|
printf ("Button1 value is sent in the periodic data.\n");
|
|
printf ("Button2 cycles through triggering an alarm, setting diagnosis and "
|
|
"creating logbook entries.\n");
|
|
printf ("\n");
|
|
printf ("Also the mandatory Profinet signal LED is controlled by this "
|
|
"application.\n");
|
|
printf ("\n");
|
|
printf ("The LEDs are controlled by the script set_profinet_leds\n");
|
|
printf ("located in the same directory as the application binary.\n");
|
|
printf ("A version for Raspberry Pi is available, and also a version "
|
|
"writing\n");
|
|
printf ("to plain text files (useful for demo if no LEDs are available).\n");
|
|
printf ("\n");
|
|
printf ("Assumes the default gateway is found on .1 on same subnet as the "
|
|
"IP address.\n");
|
|
printf ("\n");
|
|
printf ("Optional arguments:\n");
|
|
printf (" --help Show this help text and exit\n");
|
|
printf (" -h Show this help text and exit\n");
|
|
printf (" -v Incresase verbosity. Can be repeated.\n");
|
|
printf (" -f Reset to factory settings, and store to file. "
|
|
"Exit.\n");
|
|
printf (" Remember to give the -p flag if necessary.\n");
|
|
printf (" -r Remove stored files and exit.\n");
|
|
printf (" Remember to give the -p flag if necessary.\n");
|
|
printf (" -g Show stack details and exit. Repeat for more "
|
|
"details.\n");
|
|
printf (
|
|
" -i INTERF Name of Ethernet interface to use. Defaults to %s\n",
|
|
APP_DEFAULT_ETHERNET_INTERFACE);
|
|
printf (" Comma separated list if more than one interface "
|
|
"given.\n");
|
|
printf (
|
|
" -s NAME Set station name. Defaults to \"%s\". Only used\n",
|
|
APP_GSDML_DEFAULT_STATION_NAME);
|
|
printf (" if not already available in storage file.\n");
|
|
printf (" -b FILE Path (absolute or relative) to read Button1. "
|
|
"Defaults to not read Button1.\n");
|
|
printf (" -d FILE Path (absolute or relative) to read Button2. "
|
|
"Defaults to not read Button2.\n");
|
|
printf (" -p PATH Absolute path to storage directory. Defaults to "
|
|
"use current directory.\n");
|
|
#if PNET_OPTION_DRIVER_ENABLE
|
|
printf (" -m MODE Application offload mode. Only used if P-Net is\n");
|
|
printf (" built with hw offload enabled "
|
|
" (PNET_OPTION_DRIVER_ENABLE). \n");
|
|
printf (" Supported modes: none, cpu, full\n");
|
|
printf (" Defaults to none\n");
|
|
#endif
|
|
printf ("\n");
|
|
printf ("p-net revision: " PNET_VERSION "\n");
|
|
}
|
|
|
|
/**
|
|
* Parse command line arguments
|
|
*
|
|
* @param argc In: Number of arguments
|
|
* @param argv In: Arguments
|
|
* @return Parsed arguments
|
|
*/
|
|
app_args_t parse_commandline_arguments (int argc, char * argv[])
|
|
{
|
|
app_args_t output_arguments = {0};
|
|
int option;
|
|
|
|
/* Special handling of long argument */
|
|
if (argc > 1)
|
|
{
|
|
if (strcmp (argv[1], "--help") == 0)
|
|
{
|
|
show_usage();
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Default values */
|
|
strcpy (output_arguments.path_button1, "");
|
|
strcpy (output_arguments.path_button2, "");
|
|
strcpy (output_arguments.path_storage_directory, "");
|
|
strcpy (output_arguments.station_name, APP_GSDML_DEFAULT_STATION_NAME);
|
|
strcpy (output_arguments.eth_interfaces, APP_DEFAULT_ETHERNET_INTERFACE);
|
|
output_arguments.verbosity = 0;
|
|
output_arguments.show = 0;
|
|
output_arguments.factory_reset = false;
|
|
output_arguments.remove_files = false;
|
|
output_arguments.mode = MODE_HW_OFFLOAD_NONE;
|
|
|
|
while ((option = getopt (argc, argv, "hvgfri:s:b:d:p:m:")) != -1)
|
|
{
|
|
switch (option)
|
|
{
|
|
case 'v':
|
|
output_arguments.verbosity++;
|
|
break;
|
|
case 'g':
|
|
output_arguments.show++;
|
|
break;
|
|
case 'f':
|
|
output_arguments.factory_reset = true;
|
|
break;
|
|
case 'r':
|
|
output_arguments.remove_files = true;
|
|
break;
|
|
case 'i':
|
|
if ((strlen (optarg) + 1) > sizeof (output_arguments.eth_interfaces))
|
|
{
|
|
printf ("Error: The argument to -i is too long.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
strcpy (output_arguments.eth_interfaces, optarg);
|
|
break;
|
|
case 's':
|
|
strcpy (output_arguments.station_name, optarg);
|
|
break;
|
|
case 'b':
|
|
if (strlen (optarg) + 1 > PNET_MAX_FILE_FULLPATH_SIZE)
|
|
{
|
|
printf ("Error: The argument to -b is too long.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
strcpy (output_arguments.path_button1, optarg);
|
|
break;
|
|
case 'd':
|
|
if (strlen (optarg) + 1 > PNET_MAX_FILE_FULLPATH_SIZE)
|
|
{
|
|
printf ("Error: The argument to -d is too long.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
strcpy (output_arguments.path_button2, optarg);
|
|
break;
|
|
case 'p':
|
|
if (strlen (optarg) + 1 > PNET_MAX_FILE_FULLPATH_SIZE)
|
|
{
|
|
printf ("Error: The argument to -p is too long.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
strcpy (output_arguments.path_storage_directory, optarg);
|
|
break;
|
|
#if PNET_OPTION_DRIVER_ENABLE
|
|
case 'm':
|
|
if (strcmp ("none", optarg) == 0)
|
|
{
|
|
output_arguments.mode = MODE_HW_OFFLOAD_NONE;
|
|
}
|
|
else if (strcmp ("cpu", optarg) == 0)
|
|
{
|
|
output_arguments.mode = MODE_HW_OFFLOAD_CPU;
|
|
}
|
|
else if (strcmp ("full", optarg) == 0)
|
|
{
|
|
output_arguments.mode = MODE_HW_OFFLOAD_FULL;
|
|
}
|
|
else
|
|
{
|
|
printf ("Error: mode (-m) not supported.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
break;
|
|
#endif
|
|
case 'h':
|
|
/* fallthrough */
|
|
case '?':
|
|
/* fallthrough */
|
|
default:
|
|
show_usage();
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Use current directory for storage, if not given */
|
|
if (strlen (output_arguments.path_storage_directory) == 0)
|
|
{
|
|
if (
|
|
getcwd (
|
|
output_arguments.path_storage_directory,
|
|
sizeof (output_arguments.path_storage_directory)) == NULL)
|
|
{
|
|
printf ("Error: Could not read current working directory. Is "
|
|
"PNET_MAX_DIRECTORYPATH_SIZE too small?\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
return output_arguments;
|
|
}
|
|
|
|
/**
|
|
* Read a bool from a file
|
|
*
|
|
* @param filepath In: Path to file
|
|
* @return true if file exists and the first character is '1'
|
|
*/
|
|
bool read_bool_from_file (const char * filepath)
|
|
{
|
|
FILE * fp;
|
|
char ch;
|
|
int eof_indicator;
|
|
|
|
fp = fopen (filepath, "r");
|
|
if (fp == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ch = fgetc (fp);
|
|
eof_indicator = feof (fp);
|
|
fclose (fp);
|
|
|
|
if (eof_indicator)
|
|
{
|
|
return false;
|
|
}
|
|
return ch == '1';
|
|
}
|
|
|
|
bool app_get_button (uint16_t id)
|
|
{
|
|
if (id == 0)
|
|
{
|
|
if (app_args.path_button1[0] != '\0')
|
|
{
|
|
return read_bool_from_file (app_args.path_button1);
|
|
}
|
|
}
|
|
else if (id == 1)
|
|
{
|
|
if (app_args.path_button2[0] != '\0')
|
|
{
|
|
return read_bool_from_file (app_args.path_button2);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void app_set_led (uint16_t id, bool led_state)
|
|
{
|
|
/* Important:
|
|
* The Linux sample application uses a script to set the LED state,
|
|
* for easy adaption to different development boards.
|
|
*
|
|
* The script typically writes to files in the /sys directory to set LED
|
|
* state via GPIO operations. If you do not have any physical LEDs you can
|
|
* use a script that writes to regular files instead.
|
|
*
|
|
* However, file operations shall be avoided within the main task
|
|
* in a real application. File operations may affect the timing of the
|
|
* Profinet communication depending on file system implementation.
|
|
*/
|
|
|
|
char id_str[7] = {0}; /** Terminated string */
|
|
const char * argv[4];
|
|
|
|
sprintf (id_str, "%u", id);
|
|
id_str[sizeof (id_str) - 1] = '\0';
|
|
|
|
argv[0] = "set_profinet_leds";
|
|
argv[1] = (char *)&id_str;
|
|
argv[2] = (led_state == 1) ? "1" : "0";
|
|
argv[3] = NULL;
|
|
|
|
if (pnal_execute_script (argv) != 0)
|
|
{
|
|
printf ("Failed to set LED state\n");
|
|
}
|
|
}
|
|
|
|
/** Update configuration with file storage path.
|
|
* Validate this path, and Linux button file paths
|
|
*
|
|
* @param p_cfg InOut: Configuration to be updated
|
|
* @param p_args In: Command line arguments
|
|
* @return 0 on success, -1 on error.
|
|
*/
|
|
static int app_pnet_cfg_init_storage (
|
|
pnet_cfg_t * p_cfg,
|
|
const app_args_t * p_args)
|
|
{
|
|
strcpy (p_cfg->file_directory, p_args->path_storage_directory);
|
|
|
|
if (p_args->verbosity > 0)
|
|
{
|
|
printf ("Storage directory: %s\n\n", p_cfg->file_directory);
|
|
}
|
|
|
|
/* Validate paths */
|
|
if (!pnal_does_file_exist (p_cfg->file_directory))
|
|
{
|
|
printf (
|
|
"Error: The given storage directory does not exist: %s\n",
|
|
p_cfg->file_directory);
|
|
return -1;
|
|
}
|
|
|
|
if (p_args->path_button1[0] != '\0')
|
|
{
|
|
if (!pnal_does_file_exist (p_args->path_button1))
|
|
{
|
|
printf (
|
|
"Error: The given input file for Button1 does not exist: %s\n",
|
|
p_args->path_button1);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (p_args->path_button2[0] != '\0')
|
|
{
|
|
if (!pnal_does_file_exist (p_args->path_button2))
|
|
{
|
|
printf (
|
|
"Error: The given input file for Button2 does not exist: %s\n",
|
|
p_args->path_button2);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/****************************** Main ******************************************/
|
|
|
|
//int main (int argc, char * argv[])
|
|
int c_main (int argc, char * argv[])
|
|
{
|
|
int ret;
|
|
int32_t app_log_level = APP_LOG_LEVEL_FATAL;
|
|
pnet_cfg_t pnet_cfg = {0};
|
|
app_data_t * sample_app = NULL;
|
|
app_utils_netif_namelist_t netif_name_list;
|
|
pnet_if_cfg_t netif_cfg = {0};
|
|
uint16_t number_of_ports = 1;
|
|
|
|
/* Enable line buffering for printouts, especially when logging to
|
|
the journal (which is default when running as a systemd job) */
|
|
setvbuf (stdout, NULL, _IOLBF, 0);
|
|
|
|
/* Parse and display command line arguments */
|
|
app_args = parse_commandline_arguments (argc, argv);
|
|
|
|
app_log_level = (app_args.verbosity <= APP_LOG_LEVEL_FATAL)
|
|
? APP_LOG_LEVEL_FATAL - app_args.verbosity
|
|
: APP_LOG_LEVEL_DEBUG;
|
|
app_log_set_log_level (app_log_level);
|
|
printf ("\n** Starting P-Net sample application " PNET_VERSION " **\n");
|
|
|
|
APP_LOG_INFO (
|
|
"Number of slots: %u (incl slot for DAP module)\n",
|
|
PNET_MAX_SLOTS);
|
|
APP_LOG_INFO ("P-net log level: %u (DEBUG=0, FATAL=4)\n", LOG_LEVEL);
|
|
APP_LOG_INFO ("App log level: %u (DEBUG=0, FATAL=4)\n", app_log_level);
|
|
APP_LOG_INFO ("Max number of ports: %u\n", PNET_MAX_PHYSICAL_PORTS);
|
|
APP_LOG_INFO ("Network interfaces: %s\n", app_args.eth_interfaces);
|
|
APP_LOG_INFO ("Button1 file: %s\n", app_args.path_button1);
|
|
APP_LOG_INFO ("Button2 file: %s\n", app_args.path_button2);
|
|
APP_LOG_INFO ("Default station name: %s\n", app_args.station_name);
|
|
|
|
/* Prepare configuration */
|
|
app_pnet_cfg_init_default (&pnet_cfg);
|
|
strcpy (pnet_cfg.station_name, app_args.station_name);
|
|
ret = app_utils_pnet_cfg_init_netifs (
|
|
app_args.eth_interfaces,
|
|
&netif_name_list,
|
|
&number_of_ports,
|
|
&netif_cfg);
|
|
if (ret != 0)
|
|
{
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
pnet_cfg.if_cfg = netif_cfg;
|
|
pnet_cfg.num_physical_ports = number_of_ports;
|
|
|
|
app_utils_print_network_config (&netif_cfg, number_of_ports);
|
|
|
|
/* Operating system specific settings */
|
|
pnet_cfg.pnal_cfg.snmp_thread.prio = APP_SNMP_THREAD_PRIORITY;
|
|
pnet_cfg.pnal_cfg.snmp_thread.stack_size = APP_SNMP_THREAD_STACKSIZE;
|
|
pnet_cfg.pnal_cfg.eth_recv_thread.prio = APP_ETH_THREAD_PRIORITY;
|
|
pnet_cfg.pnal_cfg.eth_recv_thread.stack_size = APP_ETH_THREAD_STACKSIZE;
|
|
pnet_cfg.pnal_cfg.bg_worker_thread.prio = APP_BG_WORKER_THREAD_PRIORITY;
|
|
pnet_cfg.pnal_cfg.bg_worker_thread.stack_size =
|
|
APP_BG_WORKER_THREAD_STACKSIZE;
|
|
|
|
ret = app_pnet_cfg_init_storage (&pnet_cfg, &app_args);
|
|
if (ret != 0)
|
|
{
|
|
printf ("Failed to initialize storage.\n");
|
|
printf ("Aborting application\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/* Remove files and exit */
|
|
if (app_args.remove_files == true)
|
|
{
|
|
printf ("\nRemoving stored files\n");
|
|
printf ("Exit application\n");
|
|
(void)pnet_remove_data_files (pnet_cfg.file_directory);
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Initialise stack and application */
|
|
sample_app = app_init (&pnet_cfg, &app_args);
|
|
if (sample_app == NULL)
|
|
{
|
|
printf ("Failed to initialize P-Net.\n");
|
|
printf ("Do you have enough Ethernet interface permission?\n");
|
|
printf ("Aborting application\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/* Do factory reset and exit */
|
|
if (app_args.factory_reset == true)
|
|
{
|
|
printf ("\nPerforming factory reset\n");
|
|
printf ("Exit application\n");
|
|
(void)pnet_factory_reset (app_get_pnet_instance (sample_app));
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Show stack info and exit */
|
|
if (app_args.show > 0)
|
|
{
|
|
int level = 0xFFFF;
|
|
|
|
printf ("\nShowing stack information.\n\n");
|
|
if (app_args.show == 1)
|
|
{
|
|
level = 0x2010; /* See documentation for pnet_show() */
|
|
}
|
|
|
|
pnet_show (app_get_pnet_instance (sample_app), level);
|
|
printf ("Exit application\n");
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Start main loop */
|
|
if (app_start (sample_app, RUN_IN_SEPARATE_THREAD) != 0)
|
|
{
|
|
printf ("Failed to start\n");
|
|
printf ("Aborting application\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
os_usleep (APP_MAIN_SLEEPTIME_US);
|
|
}
|
|
|
|
return 0;
|
|
}
|