/********************************************************************* * _ _ _ * _ __ | |_ _ | | __ _ | |__ ___ * | '__|| __|(_)| | / _` || '_ \ / __| * | | | |_ _ | || (_| || |_) |\__ \ * |_| \__|(_)|_| \__,_||_.__/ |___/ * * 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 #include #include #include #include #include #include #include #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; }