Переглянути джерело

Refactor sample application

Split sample application into several files.
- Move utilities to separate file
- Add log macros for sample application
- Move gsdml parameters to specific header
- Move process data and parameters to separate file
- Add unique submodule ids
- GSDML has changed. Needs to be reloaded in engineering tool and
  ART tester.
- Align sampleapp_main for linux and rt-kernel
olabjorsne 4 роки тому
батько
коміт
07f3a5f274

+ 4 - 0
cmake/Linux.cmake

@@ -85,6 +85,10 @@ target_include_directories(pn_dev
 target_sources(pn_dev
   PRIVATE
   sample_app/sampleapp_common.c
+  sample_app/app_utils.c
+  sample_app/app_log.c
+  sample_app/app_gsdml.c
+  sample_app/app_data.c
   src/ports/linux/sampleapp_main.c
   )
 

+ 4 - 0
cmake/rt-kernel.cmake

@@ -48,6 +48,10 @@ target_include_directories(pn_dev
 target_sources(pn_dev
   PRIVATE
   sample_app/sampleapp_common.c
+  sample_app/app_utils.c
+  sample_app/app_log.c
+  sample_app/app_gsdml.c
+  sample_app/app_data.c
   src/ports/rt-kernel/sampleapp_main.c
   )
 

+ 16 - 16
sample_app/GSDML-V2.4-RT-Labs-P-Net-Sample-App-20210415.xml → sample_app/GSDML-V2.4-RT-Labs-P-Net-Sample-App-20210507.xml

@@ -23,7 +23,7 @@
       </DeviceFunction>
       <ApplicationProcess>
          <DeviceAccessPointList>
-            <DeviceAccessPointItem ID="IDD_1" PNIO_Version="V2.4" PhysicalSlots="0..4" ModuleIdentNumber="0x00000001" MinDeviceInterval="32" DNS_CompatibleName="rt-labs-dev" FixedInSlots="0" ObjectUUID_LocalIndex="1" DeviceAccessSupported="false"  MultipleWriteSupported="true" CheckDeviceID_Allowed="true" NameOfStationNotTransferable="false" LLDP_NoD_Supported="true" ResetToFactoryModes="1..2">
+            <DeviceAccessPointItem ID="IDD_1" PNIO_Version="V2.4" PhysicalSlots="0..4" ModuleIdentNumber="0x00000001" MinDeviceInterval="32" DNS_CompatibleName="rt-labs-dev" FixedInSlots="0" ObjectUUID_LocalIndex="1" DeviceAccessSupported="false" MultipleWriteSupported="true" CheckDeviceID_Allowed="true" NameOfStationNotTransferable="false" LLDP_NoD_Supported="true" ResetToFactoryModes="1..2">
                <ModuleInfo>
                   <Name TextId="IDT_MODULE_NAME_DAP1"/>
                   <InfoText TextId="IDT_INFO_DAP1"/>
@@ -35,9 +35,9 @@
                <CertificationInfo ConformanceClass="B" ApplicationClass="" NetloadClass="I"/>
                <IOConfigData MaxInputLength="244" MaxOutputLength="244"/>
                <UseableModules>
-                  <ModuleItemRef ModuleItemTarget="IDM_11" AllowedInSlots="1..4"/>
-                  <ModuleItemRef ModuleItemTarget="IDM_12" AllowedInSlots="1..4"/>
-                  <ModuleItemRef ModuleItemTarget="IDM_13" AllowedInSlots="1..4"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_30" AllowedInSlots="1..4"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_31" AllowedInSlots="1..4"/>
+                  <ModuleItemRef ModuleItemTarget="IDM_32" AllowedInSlots="1..4"/>
                </UseableModules>
                <VirtualSubmoduleList>
                   <VirtualSubmoduleItem ID="IDS_1" SubmoduleIdentNumber="0x00000001" Writeable_IM_Records="1 2 3" MayIssueProcessAlarm="false">
@@ -56,7 +56,7 @@
                   </InterfaceSubmoduleItem>
                   <PortSubmoduleItem ID="IDS_P1" SubmoduleIdentNumber="0x00008001" SubslotNumber="32769" TextId="IDT_NAME_PS1" MaxPortRxDelay="350" MaxPortTxDelay="160">
                      <MAUTypeList>
-<!--
+                        <!--
 MAUTypeItems shall match the actual network interfaces of the device.
 Current list works for Raspberry Pi, Linksys usb/ethernet dongle and xmc sample targets
 -->
@@ -65,11 +65,11 @@ Current list works for Raspberry Pi, Linksys usb/ethernet dongle and xmc sample
                         <MAUTypeItem Value="5"/>
                      </MAUTypeList>
                   </PortSubmoduleItem>
-<!--
+                  <!--
 Enable to support additional port. (PNET_MAX_PHYSICAL_PORTS == 2)
 Add additional PortSubmoduleItems to support additional ports
 -->
-<!--
+                  <!--
                   <PortSubmoduleItem ID="IDS_P2" SubmoduleIdentNumber="0x00008002" SubslotNumber="32770" TextId="IDT_NAME_PS2" MaxPortRxDelay="350" MaxPortTxDelay="160">
                      <MAUTypeList>
                         <MAUTypeItem Value="30"/>
@@ -85,7 +85,7 @@ Add additional PortSubmoduleItems to support additional ports
             </DeviceAccessPointItem>
          </DeviceAccessPointList>
          <ModuleList>
-            <ModuleItem ID="IDM_11" ModuleIdentNumber="0x00000030">
+            <ModuleItem ID="IDM_30" ModuleIdentNumber="0x00000030">
                <ModuleInfo>
                   <Name TextId="TOK_Name_Module_I8"/>
                   <InfoText TextId="TOK_InfoText_Module_I8"/>
@@ -93,7 +93,7 @@ Add additional PortSubmoduleItems to support additional ports
                   <SoftwareRelease Value="1.0"/>
                </ModuleInfo>
                <VirtualSubmoduleList>
-                  <VirtualSubmoduleItem ID="11" SubmoduleIdentNumber="0x0001" MayIssueProcessAlarm="true">
+                  <VirtualSubmoduleItem ID="IDSM_130" SubmoduleIdentNumber="0x0130" MayIssueProcessAlarm="true">
                      <IOData>
                         <Input Consistency="All items consistency">
                            <DataItem DataType="Unsigned8" TextId="TOK_Input_DataItem_8" UseAsBits="true">
@@ -106,7 +106,7 @@ Add additional PortSubmoduleItems to support additional ports
                               <BitDataItem BitOffset="6" TextId="TOK_Input_DataItem_Bit6"/>
                               <BitDataItem BitOffset="7" TextId="TOK_Input_DataItem_Bit7"/>
                            </DataItem>
-                         </Input>
+                        </Input>
                      </IOData>
                      <ModuleInfo>
                         <Name TextId="TOK_Name_Module_I8"/>
@@ -115,7 +115,7 @@ Add additional PortSubmoduleItems to support additional ports
                   </VirtualSubmoduleItem>
                </VirtualSubmoduleList>
             </ModuleItem>
-            <ModuleItem ID="IDM_12" ModuleIdentNumber="0x00000031">
+            <ModuleItem ID="IDM_31" ModuleIdentNumber="0x00000031">
                <ModuleInfo>
                   <Name TextId="TOK_Name_Module_O8"/>
                   <InfoText TextId="TOK_InfoText_Module_O8"/>
@@ -123,7 +123,7 @@ Add additional PortSubmoduleItems to support additional ports
                   <SoftwareRelease Value="1.0"/>
                </ModuleInfo>
                <VirtualSubmoduleList>
-                  <VirtualSubmoduleItem ID="12" SubmoduleIdentNumber="0x0001" MayIssueProcessAlarm="true">
+                  <VirtualSubmoduleItem ID="IDSM_131" SubmoduleIdentNumber="0x0131" MayIssueProcessAlarm="true">
                      <IOData>
                         <Output Consistency="All items consistency">
                            <DataItem DataType="Unsigned8" TextId="TOK_Output_DataItem_8" UseAsBits="true">
@@ -144,8 +144,8 @@ Add additional PortSubmoduleItems to support additional ports
                      </ModuleInfo>
                   </VirtualSubmoduleItem>
                </VirtualSubmoduleList>
-               </ModuleItem>
-            <ModuleItem ID="IDM_13" ModuleIdentNumber="0x00000032">
+            </ModuleItem>
+            <ModuleItem ID="IDM_32" ModuleIdentNumber="0x00000032">
                <ModuleInfo>
                   <Name TextId="TOK_Name_Module_I8O8"/>
                   <InfoText TextId="TOK_InfoText_Module_I8O8"/>
@@ -153,7 +153,7 @@ Add additional PortSubmoduleItems to support additional ports
                   <SoftwareRelease Value="1.0"/>
                </ModuleInfo>
                <VirtualSubmoduleList>
-                  <VirtualSubmoduleItem ID="13" SubmoduleIdentNumber="0x0001" MayIssueProcessAlarm="true">
+                  <VirtualSubmoduleItem ID="IDSM_132" SubmoduleIdentNumber="0x0132" MayIssueProcessAlarm="true">
                      <IOData>
                         <Input>
                            <DataItem DataType="Unsigned8" UseAsBits="true" TextId="TOK_Input_DataItem_8">
@@ -225,7 +225,7 @@ Add additional PortSubmoduleItems to support additional ports
                <!--module name-->
                <Text TextId="TOK_Name_Module_I8" Value="DI 8xLogicLevel"/>
                <Text TextId="TOK_Name_Module_O8" Value="DO 8xLogicLevel"/>
-               <Text TextId="TOK_Name_Module_I8O8" Value="DIO 8xLogicLevel "/>
+               <Text TextId="TOK_Name_Module_I8O8" Value="DIO 8xLogicLevel"/>
                <!--module info -->
                <Text TextId="TOK_InfoText_Module_I8" Value="Digital In 8xLogicLevel"/>
                <Text TextId="TOK_InfoText_Module_O8" Value="Digital Out 8xLogicLevel"/>

+ 221 - 0
sample_app/app_data.c

@@ -0,0 +1,221 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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.
+ ********************************************************************/
+
+#include "app_data.h"
+#include "app_utils.h"
+#include "app_gsdml.h"
+#include "app_log.h"
+#include "sampleapp_common.h"
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define APP_DATA_DEFAULT_OUTPUT_DATA 0
+
+/* Parameters data
+ * Todo: Data is always in pnio data format. Add conversion to uint32_t.
+ */
+uint32_t app_param_1 = 0;
+uint32_t app_param_2 = 0;
+
+/* Process data */
+uint8_t inputdata[APP_GSDML_INPUT_DATA_SIZE] = {0};
+uint8_t outputdata[APP_GSDML_OUTPUT_DATA_SIZE] = {0};
+
+/**
+ * Set LED state.
+ * Compares new state with previous state to avoid disk operations.
+ * Important:
+ * By default the sample application uses files to represent
+ * the LED status. File operations shall be avoided within the main
+ * task as they may affect the timing of the profinet communication
+ * depending on file system implementation.
+ *
+ * if the GPIOs file are not
+ *
+ * @param led_state        In:    New LED state
+ */
+static void app_handle_data_led_state (bool led_state)
+{
+   static bool previous_led_state = false;
+
+   if (led_state != previous_led_state)
+   {
+      app_set_led (APP_DATA_LED_ID, led_state);
+   }
+   previous_led_state = led_state;
+}
+
+uint8_t * app_data_get_input_data (
+   uint32_t submodule_id,
+   bool button_pressed,
+   uint8_t counter,
+   uint16_t * size,
+   uint8_t * iops)
+{
+   if (size == NULL || iops == NULL)
+   {
+      return NULL;
+   }
+
+   if (
+      submodule_id != APP_GSDML_SUBMOD_ID_DIGITAL_IN &&
+      submodule_id != APP_GSDML_SUBMOD_ID_DIGITAL_IN_OUT)
+   {
+      /* Automated RT Tester scenario 2 - unsupported (sub)module */
+      *iops = PNET_IOXS_BAD;
+      return NULL;
+   }
+
+   /* Prepare input data
+    * Lowest 7 bits: Counter    Most significant bit: Button
+    */
+   inputdata[0] = counter;
+   if (button_pressed)
+   {
+      inputdata[0] |= 0x80;
+   }
+   else
+   {
+      inputdata[0] &= 0x7F;
+   }
+
+   *size = APP_GSDML_INPUT_DATA_SIZE;
+   *iops = PNET_IOXS_GOOD;
+
+   return inputdata;
+}
+
+int app_data_set_output_data (
+   uint32_t submodule_id,
+   uint8_t * data,
+   uint16_t size)
+{
+   bool led_state;
+
+   if (data != NULL && size == APP_GSDML_OUTPUT_DATA_SIZE)
+   {
+      if (
+         submodule_id == APP_GSDML_SUBMOD_ID_DIGITAL_OUT ||
+         submodule_id == APP_GSDML_SUBMOD_ID_DIGITAL_IN_OUT)
+      {
+         led_state = (outputdata[0] & 0x80) > 0;
+         app_handle_data_led_state (led_state);
+         return 0;
+      }
+   }
+   return -1;
+}
+
+int app_data_set_default_outputs (void)
+{
+   outputdata[0] = APP_DATA_DEFAULT_OUTPUT_DATA;
+   app_handle_data_led_state (false);
+}
+
+int app_data_write_parameter (
+   uint32_t submodule_id,
+   uint32_t index,
+   const uint8_t * data,
+   uint16_t length)
+{
+   const app_gsdml_param_t * par_cfg;
+
+   par_cfg = app_gsdml_get_parameter_cfg (submodule_id, index);
+   if (par_cfg == NULL)
+   {
+      APP_LOG_WARNING (
+         "PLC write request unsupported submodule/parameter. "
+         "Submodule id: %u Index: %u\n",
+         (unsigned)submodule_id,
+         (unsigned)index);
+      return -1;
+   }
+
+   if (length != par_cfg->length)
+   {
+      APP_LOG_WARNING (
+         "PLC write request unsupported length. "
+         "Index: %u Length: %u Expected length: %u\n",
+         (unsigned)index,
+         (unsigned)length,
+         par_cfg->length);
+      return -1;
+   }
+
+   if (index == APP_GSDM_PARAMETER_1_IDX)
+   {
+      memcpy (&app_param_1, data, sizeof (length));
+   }
+   else if (index == APP_GSDM_PARAMETER_2_IDX)
+   {
+      memcpy (&app_param_2, data, sizeof (length));
+   }
+   APP_LOG_DEBUG ("  Writing %s\n", par_cfg->name);
+   app_log_print_bytes (APP_LOG_LEVEL_DEBUG, data, length);
+
+   return 0;
+}
+
+int app_data_read_parameter (
+   uint32_t submodule_id,
+   uint32_t index,
+   uint8_t ** data,
+   uint16_t * length)
+{
+   const app_gsdml_param_t * par_cfg;
+
+   par_cfg = app_gsdml_get_parameter_cfg (submodule_id, index);
+   if (par_cfg == NULL)
+   {
+      APP_LOG_WARNING (
+         "PLC read request unsupported submodule/parameter. "
+         "Submodule id: %u Index: %u\n",
+         (unsigned)submodule_id,
+         (unsigned)index);
+      return -1;
+   }
+
+   if (*length < par_cfg->length)
+   {
+      APP_LOG_WARNING (
+         "PLC read request unsupported length. "
+         "Index: %u Length: %u Expected length: %u\n",
+         (unsigned)index,
+         (unsigned)*length,
+         par_cfg->length);
+      return -1;
+   }
+
+   APP_LOG_DEBUG ("  Reading %s\n", par_cfg->name);
+   if (index == APP_GSDM_PARAMETER_1_IDX)
+   {
+      *data = (uint8_t *)&app_param_1;
+      *length = sizeof (app_param_1);
+   }
+   else if (index == APP_GSDM_PARAMETER_2_IDX)
+   {
+      *data = (uint8_t *)&app_param_2;
+      *length = sizeof (app_param_2);
+   }
+
+   app_log_print_bytes (APP_LOG_LEVEL_DEBUG, *data, *length);
+
+   return 0;
+}

+ 123 - 0
sample_app/app_data.h

@@ -0,0 +1,123 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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 APP_DATA_H
+#define APP_DATA_H
+
+/**
+ * @file
+ * @brief Sample application data interface
+ *
+ * Functions for:
+ * - Getting input data (Button 1 and counter value)
+ * - Setting ouput data (LED 1)
+ * - Setting default output state. This should be
+ *   part of all device implementations for setting
+ *   defined state when device is not connected to PLC
+ * - Reading and writing parameters
+ *
+ * Todo:
+ * Currently sample application uses a single byte
+ * for both input and output data. Add float and other
+ * types.
+ * Currently sample application never convert parameters
+ * to native uint32_t value. Fix this.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Get PNIO input data using module id.
+ * The main sample application keep track
+ * of button and counter state so these are
+ * parameters to this function.
+ * @param submodule_id  In:  Submodule id
+ * @param button_state  In:  State of button 1
+ * @param counter       In:  Sample app counter value
+ * @param size          Out: Size of pnio data
+ * @param iops          Out: Provider status. If for example
+ *                           a sensor is failing or a short
+ *                           circuit detected on digital
+ *                           input this shall be set to BAD.
+ * @return Reference to PNIO data, NULL on error
+ */
+uint8_t * app_data_get_input_data (
+   uint32_t submodule_id,
+   bool button_state,
+   uint8_t counter,
+   uint16_t * size,
+   uint8_t * iops);
+
+/**
+ * Set PNIO output data using module id.
+ * @param submodule_id  In:  Submodule id
+ * @param data          In:  Reference to output data
+ * @param size          In:  Length of output data
+ * @return 0 on success, -1 on error
+ */
+int app_data_set_output_data (
+   uint32_t submodule_id,
+   uint8_t * data,
+   uint16_t size);
+
+/**
+ * Set default outputs.
+ * For the sample application this means that
+ * LED 1 is turned off.
+ * @param submodule_id  In:  Submodule id
+ * @param data          In:  Reference to output data
+ * @param size          In:  Length of output data
+ * @return 0 on success, -1 on error
+ */
+int app_data_set_default_outputs (void);
+
+/**
+ * Write parameter index.
+ * @param submodule_id  In:  Submodule id
+ * @param index         In:  Parameter index
+ * @param data          In:  New parameter value
+ * @param size          In:  Length of parameter data
+ * @return 0 on success, -1 on error
+ */
+int app_data_write_parameter (
+   uint32_t submodule_id,
+   uint32_t index,
+   const uint8_t * data,
+   uint16_t write_length);
+
+/**
+ * Read parameter index.
+ * @param submodule_id  In:  Submodule id
+ * @param index         In:  Parameter index
+ * @param data          In:  Refernce to parameter data
+ * @param size          In:  Length of parameter data
+ * @return 0 on success, -1 on error
+ */
+int app_data_read_parameter (
+   uint32_t submodule_id,
+   uint32_t index,
+   uint8_t ** data,
+   uint16_t * length);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_DATA_H */

+ 86 - 0
sample_app/app_gsdml.c

@@ -0,0 +1,86 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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.
+ ********************************************************************/
+
+#include "sampleapp_common.h"
+#include "app_utils.h"
+#include "app_gsdml.h"
+#include "app_log.h"
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+const app_gsdml_module_t * app_gsdml_get_module_cfg (uint32_t id)
+{
+   uint32_t i;
+   for (i = 0; i < NELEMENTS (app_gsdml_modules); i++)
+   {
+      if (app_gsdml_modules[i]->id == id)
+      {
+         return app_gsdml_modules[i];
+      }
+   }
+   return NULL;
+}
+
+const app_gsdml_submodule_t * app_gsdml_get_submodule_cfg (uint32_t id)
+{
+   uint32_t i;
+   for (i = 0; i < NELEMENTS (app_gsdml_submodules); i++)
+   {
+      if (app_gsdml_submodules[i]->id == id)
+      {
+         return app_gsdml_submodules[i];
+      }
+   }
+   return NULL;
+}
+
+const app_gsdml_param_t * app_gsdml_get_parameter_cfg (
+   uint32_t submodule_id,
+   uint32_t index)
+{
+   uint16_t i, j;
+
+   const app_gsdml_submodule_t * submodule_cfg =
+      app_gsdml_get_submodule_cfg (submodule_id);
+
+   if (submodule_cfg == NULL)
+   {
+      /* Unsupported submodule id */
+      return NULL;
+   }
+
+   /* Search for parameter index in submodule configuration */
+   for (i = 0; submodule_cfg->parameters[i] != 0; i++)
+   {
+      if (submodule_cfg->parameters[i] == index)
+      {
+         /* Find parameter configuration */
+         for (j = 0; j < NELEMENTS (app_gsdml_parameters); j++)
+         {
+            if (app_gsdml_parameters[j].index == index)
+            {
+               return &app_gsdml_parameters[j];
+            }
+         }
+      }
+   }
+
+   return NULL;
+}

+ 289 - 0
sample_app/app_gsdml.h

@@ -0,0 +1,289 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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 APP_GSDML_H
+#define APP_GSDML_H
+
+/**
+ * @file
+ * @brief Device properties defined by the GSDML device definition
+ *
+ * Functions for getting module, submodule and parameter
+ * configurations using their ids.
+ *
+ * Important:
+ * Any change in this file may require an update of the GSDML file.
+ * Note that when the GSDML file is updated it has to be reloaded
+ * in your Profinet engineering tool. PLC applications may be affected.
+ *
+ * Design requires unique submodule ids.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pnet_api.h>
+
+#define APP_GSDML_API 0
+
+#define APP_GSDML_DEFAULT_STATION_NAME "rt-labs-dev"
+
+/* GSDML tag: VendorID */
+#define APP_GSDML_VENDOR_ID 0xfeed
+
+/* GSDML tag: DeviceID */
+#define APP_GSDML_DEVICE_ID 0xbeef
+
+/* Allowed: 'V', 'R', 'P', 'U', 'T' */
+#define APP_GSDML_SW_REV_PREFIX     'V'
+#define APP_GSDML_PROFILE_ID        0x1234
+#define APP_GSDML_PROFILE_SPEC_TYPE 0x5678
+
+/* GSDML tag: Writeable_IM_Records */
+#define APP_GSDML_IM_SUPPORTED                                                 \
+   (PNET_SUPPORTED_IM1 | PNET_SUPPORTED_IM2 | PNET_SUPPORTED_IM3)
+
+/* GSDML tag: OrderNumber */
+#define APP_GSDML_ORDER_ID "12345 Abcdefghijk"
+/* GSDML tag: ModuleInfo / Name */
+#define APP_GSDML_PRODUCT_NAME "P-Net Sample Application"
+
+/* GSDML tag: MinDeviceInterval */
+#define APP_GSDML_MIN_DEVICE_INTERVAL 32 /* 1ms */
+
+#define APP_GSDML_DIAG_CUSTOM_USI 0x1234
+
+/* See "Specification for GSDML" 8.26 LogBookEntryItem for allowed values */
+#define APP_GSDML_LOGBOOK_ERROR_CODE   0x20 /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ERROR_DECODE 0x82 /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ERROR_CODE_1 PNET_ERROR_CODE_1_FSPM
+#define APP_GSDML_LOGBOOK_ERROR_CODE_2 0x00       /* Manufacturer specific */
+#define APP_GSDML_LOGBOOK_ENTRY_DETAIL 0xFEE1DEAD /* Manufacturer specific */
+
+#define APP_GSDM_PARAMETER_1_IDX  123
+#define APP_GSDM_PARAMETER_2_IDX  124
+#define APP_GSDM_PARAMETER_LENGTH 4
+
+typedef struct cfg_module
+{
+   uint32_t id;
+   const char * name;
+   uint32_t submodules[]; /* Variable length, ends with 0*/
+} app_gsdml_module_t;
+
+typedef struct app_gsdml_submodule
+{
+   uint32_t id;
+   const char * name;
+   uint32_t api;
+   pnet_submodule_dir_t data_dir;
+   uint16_t insize;
+   uint16_t outsize;
+   uint16_t parameters[]; /* Variable length, ends with 0 */
+} app_gsdml_submodule_t;
+
+typedef struct
+{
+   uint32_t index;
+   const char * name;
+   uint16_t length;
+} app_gsdml_param_t;
+
+#define APP_GSDML_MOD_ID_8_0_DIGITAL_IN     0x00000030
+#define APP_GSDML_MOD_ID_0_8_DIGITAL_OUT    0x00000031
+#define APP_GSDML_MOD_ID_8_8_DIGITAL_IN_OUT 0x00000032
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN      0x00000130
+#define APP_GSDML_SUBMOD_ID_DIGITAL_OUT     0x00000131
+#define APP_GSDML_SUBMOD_ID_DIGITAL_IN_OUT  0x00000132
+
+#define APP_GSDML_INPUT_DATA_SIZE   1 /* bytes, for digital inputs data */
+#define APP_GSDML_OUTPUT_DATA_SIZE  1 /* bytes, for digital outputs data */
+#define APP_GSDM_ALARM_PAYLOAD_SIZE 1 /* bytes */
+
+static const app_gsdml_module_t dap_1 = {
+   .id = PNET_MOD_DAP_IDENT,
+   .name = "DAP 1",
+   .submodules = {
+      PNET_SUBMOD_DAP_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_3_IDENT,
+      PNET_SUBMOD_DAP_INTERFACE_1_PORT_4_IDENT,
+      0}};
+
+static const app_gsdml_module_t module_digital_in = {
+   .id = APP_GSDML_MOD_ID_8_0_DIGITAL_IN,
+   .name = "DI 8xLogicLevel",
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN, 0},
+};
+
+static const app_gsdml_module_t module_digital_out = {
+   .id = APP_GSDML_MOD_ID_0_8_DIGITAL_OUT,
+   .name = "DO 8xLogicLevel",
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_OUT, 0}};
+
+static const app_gsdml_module_t module_digital_in_out = {
+   .id = APP_GSDML_MOD_ID_8_8_DIGITAL_IN_OUT,
+   .name = "DIO 8xLogicLevel",
+   .submodules = {APP_GSDML_SUBMOD_ID_DIGITAL_IN_OUT, 0}};
+
+static const app_gsdml_submodule_t dap_indentity_1 = {
+   .name = "DAP Identity 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_interface_1 = {
+   .name = "DAP Interface 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_1 = {
+   .name = "DAP Port 1",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_2 = {
+   .name = "DAP Port 2",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_3 = {
+   .name = "DAP Port 3",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_3_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t dap_port_4 = {
+   .name = "DAP Port 4",
+   .api = APP_GSDML_API,
+   .id = PNET_SUBMOD_DAP_INTERFACE_1_PORT_4_IDENT,
+   .data_dir = PNET_DIR_NO_IO,
+   .insize = 0,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submod_digital_in = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN,
+   .name = "Digital Input",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_INPUT,
+   .insize = APP_GSDML_INPUT_DATA_SIZE,
+   .outsize = 0,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submod_digital_out = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_OUT,
+   .name = "Digital Output",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_OUTPUT,
+   .insize = 0,
+   .outsize = APP_GSDML_OUTPUT_DATA_SIZE,
+   .parameters = {0}};
+
+static const app_gsdml_submodule_t submod_digital_inout = {
+   .id = APP_GSDML_SUBMOD_ID_DIGITAL_IN_OUT,
+   .name = "Digital Input/Output",
+   .api = APP_GSDML_API,
+   .data_dir = PNET_DIR_IO,
+   .insize = APP_GSDML_INPUT_DATA_SIZE,
+   .outsize = APP_GSDML_OUTPUT_DATA_SIZE,
+   .parameters = {APP_GSDM_PARAMETER_1_IDX, APP_GSDM_PARAMETER_2_IDX, 0}};
+
+/* List of supported modules */
+static const app_gsdml_module_t * app_gsdml_modules[] =
+   {&dap_1, &module_digital_in, &module_digital_out, &module_digital_in_out};
+
+/* List of supported submodules */
+static const app_gsdml_submodule_t * app_gsdml_submodules[] = {
+   &dap_indentity_1,
+   &dap_interface_1,
+   &dap_port_1,
+   &dap_port_2,
+   &dap_port_3,
+   &dap_port_4,
+
+   &submod_digital_in,
+   &submod_digital_out,
+   &submod_digital_inout,
+};
+
+/* List of supported parameters.
+ * Note that paramerers are submodule attribute.
+ * This list contain all parameters while each
+ * submodule list its supported parameters using
+ * their indexes.
+ */
+static app_gsdml_param_t app_gsdml_parameters[] = {
+   {
+      .index = APP_GSDM_PARAMETER_1_IDX,
+      .name = "Demo 1",
+      .length = APP_GSDM_PARAMETER_LENGTH,
+   },
+   {
+      .index = APP_GSDM_PARAMETER_2_IDX,
+      .name = "Demo 2",
+      .length = APP_GSDM_PARAMETER_LENGTH,
+   }};
+
+/**
+ * Get module configuration from module id
+ * @param module_id  In: Module id
+ * @return Module configuration, NULL if not found
+ */
+const app_gsdml_module_t * app_gsdml_get_module_cfg (uint32_t module_id);
+
+/**
+ * Get submodule module configuration from submodule id
+ * @param submodule_id  In: Submodule id
+ * @return Submodule configuration, NULL if not found
+ */
+const app_gsdml_submodule_t * app_gsdml_get_submodule_cfg (
+   uint32_t submodule_id);
+
+/**
+ * Get parameter configuration from parameter index
+ * @param submodule_id  In: Submodule id
+ * @param index         In: Parameters index
+ * @return Parameter configuration, NULL if not found
+ */
+const app_gsdml_param_t * app_gsdml_get_parameter_cfg (
+   uint32_t submodule_id,
+   uint32_t index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_GSDML_H */

+ 52 - 0
sample_app/app_log.c

@@ -0,0 +1,52 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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.
+ ********************************************************************/
+
+#include "app_log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+static int32_t log_level = APP_DEFAULT_LOG_LEVEL;
+
+void app_log_set_log_level (int32_t level)
+{
+   log_level = level;
+}
+
+void app_log (int32_t level, const char * fmt, ...)
+{
+   va_list list;
+
+   if (level >= log_level)
+   {
+      va_start (list, fmt);
+      vprintf (fmt, list);
+      va_end (list);
+      fflush (stdout);
+   }
+}
+
+void app_log_print_bytes (int32_t level, const uint8_t * bytes, uint32_t len)
+{
+   if (level >= log_level)
+   {
+      printf ("  Bytes: ");
+      for (int i = 0; i < len; i++)
+      {
+         printf ("%02X ", bytes[i]);
+      }
+      printf ("\n");
+   }
+}

+ 75 - 0
sample_app/app_log.h

@@ -0,0 +1,75 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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 APP_LOG_H
+#define APP_LOG_H
+
+/**
+ * @file
+ * @brief Application debug log utility
+ *
+ * Runtime configurable debug log using printf()
+ * Levels matches levels used in P-Net.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#define APP_LOG_LEVEL_DEBUG   0x00
+#define APP_LOG_LEVEL_INFO    0x01
+#define APP_LOG_LEVEL_WARNING 0x02
+#define APP_LOG_LEVEL_ERROR   0x03
+#define APP_LOG_LEVEL_FATAL   0x04
+
+#define APP_DEFAULT_LOG_LEVEL APP_LOG_LEVEL_FATAL
+
+#define APP_LOG(level, ...) app_log (level, __VA_ARGS__)
+
+#define APP_LOG_DEBUG(...)   APP_LOG (APP_LOG_LEVEL_DEBUG, __VA_ARGS__)
+#define APP_LOG_INFO(...)    APP_LOG (APP_LOG_LEVEL_INFO, __VA_ARGS__)
+#define APP_LOG_WARNING(...) APP_LOG (APP_LOG_LEVEL_WARNING, __VA_ARGS__)
+#define APP_LOG_ERROR(...)   APP_LOG (APP_LOG_LEVEL_ERROR, __VA_ARGS__)
+#define APP_LOG_FATAL(...)   APP_LOG (APP_LOG_LEVEL_FATAL, __VA_ARGS__)
+
+/**
+ * Print log message depending on level
+ * Use the APP_LOG_xxxxx macros instead of this function.
+ * @param level         In: Message log level
+ * @param fmt           In: Log message format string
+ */
+void app_log (int32_t level, const char * fmt, ...);
+
+/**
+ * Log an array of bytes
+ * @param level         In: Log level
+ * @param bytes         In: Array of bytes
+ * @param length        In: Length of array
+ */
+void app_log_print_bytes (int32_t level, const uint8_t * bytes, uint32_t length);
+
+/**
+ * Set log level
+ * @param level         In: Log level
+ */
+void app_log_set_log_level (int32_t level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_LOG_H */

+ 714 - 0
sample_app/app_utils.c

@@ -0,0 +1,714 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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.
+ ********************************************************************/
+
+#define _GNU_SOURCE /* For asprintf() */
+
+#include "app_utils.h"
+#include "app_log.h"
+#include "app_gsdml.h"
+#include "sampleapp_common.h"
+#include "osal.h"
+#include "osal_log.h" /* For LOG_LEVEL */
+#include "pnal.h"
+#include <pnet_api.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define GET_HIGH_BYTE(id) ((id >> 8) & 0xFF)
+#define GET_LOW_BYTE(id)  (id & 0xFF)
+
+void app_utils_ip_to_string (pnal_ipaddr_t ip, char * outputstring)
+{
+   snprintf (
+      outputstring,
+      PNAL_INET_ADDRSTR_SIZE,
+      "%u.%u.%u.%u",
+      (uint8_t) ((ip >> 24) & 0xFF),
+      (uint8_t) ((ip >> 16) & 0xFF),
+      (uint8_t) ((ip >> 8) & 0xFF),
+      (uint8_t) (ip & 0xFF));
+}
+
+void app_utils_mac_to_string (pnet_ethaddr_t mac, char * outputstring)
+{
+   snprintf (
+      outputstring,
+      PNAL_ETH_ADDRSTR_SIZE,
+      "%02X:%02X:%02X:%02X:%02X:%02X",
+      mac.addr[0],
+      mac.addr[1],
+      mac.addr[2],
+      mac.addr[3],
+      mac.addr[4],
+      mac.addr[5]);
+}
+
+const char * app_utils_submod_dir_to_string (pnet_submodule_dir_t direction)
+{
+   const char * s = "<error>";
+
+   switch (direction)
+   {
+   case PNET_DIR_NO_IO:
+      s = "NO_IO";
+      break;
+   case PNET_DIR_INPUT:
+      s = "INPUT";
+      break;
+   case PNET_DIR_OUTPUT:
+      s = "OUTPUT";
+      break;
+   case PNET_DIR_IO:
+      s = "INPUT_OUTPUT";
+      break;
+   }
+
+   return s;
+}
+
+const char * app_utils_ioxs_to_string (pnet_ioxs_values_t ioxs)
+{
+   const char * s = "<error>";
+   switch (ioxs)
+   {
+   case PNET_IOXS_BAD:
+      s = "IOXS_BAD";
+      break;
+   case PNET_IOXS_GOOD:
+      s = "IOXS_GOOD";
+      break;
+   }
+
+   return s;
+}
+
+void app_utils_get_error_code_strings (
+   uint16_t err_cls,
+   uint16_t err_code,
+   const char ** err_cls_str,
+   const char ** err_code_str)
+{
+   if (err_cls_str == NULL || err_cls_str == NULL)
+   {
+      return;
+   }
+   *err_cls_str = "Not decoded";
+   *err_code_str = "Not decoded";
+
+   switch (err_cls)
+   {
+   case PNET_ERROR_CODE_1_RTA_ERR_CLS_PROTOCOL:
+      *err_cls_str = "Real-Time Acyclic Protocol";
+      switch (err_code)
+      {
+      case PNET_ERROR_CODE_2_ABORT_AR_CONSUMER_DHT_EXPIRED:
+         *err_code_str = "Device missed cyclic data "
+                         "deadline, device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_AR_CMI_TIMEOUT:
+         *err_code_str = "Communication initialization "
+                         "timeout, device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_AR_RELEASE_IND_RECEIVED:
+         *err_code_str = "AR release indication received";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_DCP_STATION_NAME_CHANGED:
+         *err_code_str = "DCP station name changed, "
+                         "device terminated AR";
+         break;
+      case PNET_ERROR_CODE_2_ABORT_DCP_RESET_TO_FACTORY:
+         *err_code_str = "DCP reset to factory or factory "
+                         "reset, device terminated AR";
+         break;
+      }
+      break;
+
+   case PNET_ERROR_CODE_1_CTLDINA:
+      *err_cls_str = "CTLDINA = Name and IP assignment from controller";
+      switch (err_code)
+      {
+      case PNET_ERROR_CODE_2_CTLDINA_ARP_MULTIPLE_IP_ADDRESSES:
+         *err_code_str = "Multiple users of same IP address";
+         break;
+      }
+      break;
+   }
+}
+void app_utils_copy_ip_to_struct (
+   pnet_cfg_ip_addr_t * destination_struct,
+   pnal_ipaddr_t ip)
+{
+   destination_struct->a = ((ip >> 24) & 0xFF);
+   destination_struct->b = ((ip >> 16) & 0xFF);
+   destination_struct->c = ((ip >> 8) & 0xFF);
+   destination_struct->d = (ip & 0xFF);
+}
+
+const char * app_utils_dcontrol_cmd_to_string (
+   pnet_control_command_t control_command)
+{
+   const char * s = NULL;
+
+   switch (control_command)
+   {
+   case PNET_CONTROL_COMMAND_PRM_BEGIN:
+      s = "PRM_BEGIN";
+      break;
+   case PNET_CONTROL_COMMAND_PRM_END:
+      s = "PRM_END";
+      break;
+   case PNET_CONTROL_COMMAND_APP_RDY:
+      s = "APP_RDY";
+      break;
+   case PNET_CONTROL_COMMAND_RELEASE:
+      s = "RELEASE";
+      break;
+   default:
+      s = "<error>";
+      break;
+   }
+
+   return s;
+}
+
+const char * app_utils_event_to_string (pnet_event_values_t event)
+{
+   const char * s = "<error>";
+
+   switch (event)
+   {
+   case PNET_EVENT_ABORT:
+      s = "PNET_EVENT_ABORT";
+      break;
+   case PNET_EVENT_STARTUP:
+      s = "PNET_EVENT_STARTUP";
+      break;
+   case PNET_EVENT_PRMEND:
+      s = "PNET_EVENT_PRMEND";
+      break;
+   case PNET_EVENT_APPLRDY:
+      s = "PNET_EVENT_APPLRDY";
+      break;
+   case PNET_EVENT_DATA:
+      s = "PNET_EVENT_DATA";
+      break;
+   }
+
+   return s;
+}
+
+int app_utils_pnet_cfg_init_default (pnet_cfg_t * cfg)
+{
+   memset (cfg, 0, sizeof (pnet_cfg_t));
+
+   cfg->tick_us = APP_TICK_INTERVAL_US;
+
+   /* Identification & Maintenance */
+
+   cfg->im_0_data.im_vendor_id_hi = GET_HIGH_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->im_0_data.im_vendor_id_lo = GET_LOW_BYTE (APP_GSDML_VENDOR_ID);
+
+   cfg->im_0_data.im_hardware_revision = 1;
+   cfg->im_0_data.im_sw_revision_prefix = APP_GSDML_SW_REV_PREFIX;
+   cfg->im_0_data.im_sw_revision_functional_enhancement = PNET_VERSION_MAJOR;
+   cfg->im_0_data.im_sw_revision_bug_fix = PNET_VERSION_MINOR;
+   cfg->im_0_data.im_sw_revision_internal_change = PNET_VERSION_PATCH;
+   cfg->im_0_data.im_revision_counter = 0; /* Only 0 allowed according
+                                                       to standard */
+   cfg->im_0_data.im_profile_id = APP_GSDML_PROFILE_ID;
+   cfg->im_0_data.im_profile_specific_type = APP_GSDML_PROFILE_SPEC_TYPE;
+   cfg->im_0_data.im_version_major = 1;
+   cfg->im_0_data.im_version_minor = 1;
+   cfg->im_0_data.im_supported = APP_GSDML_IM_SUPPORTED;
+   strcpy (cfg->im_0_data.im_order_id, APP_GSDML_ORDER_ID);
+   strcpy (cfg->im_0_data.im_serial_number, "00001");
+   strcpy (cfg->im_1_data.im_tag_function, "my function");
+   strcpy (cfg->im_1_data.im_tag_location, "my location");
+   strcpy (cfg->im_2_data.im_date, "2020-09-03 13:53");
+   strcpy (cfg->im_3_data.im_descriptor, "my descriptor");
+   strcpy (cfg->im_4_data.im_signature, ""); /* Functional safety */
+
+   /* Device configuration */
+   cfg->device_id.vendor_id_hi = GET_HIGH_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->device_id.vendor_id_lo = GET_LOW_BYTE (APP_GSDML_VENDOR_ID);
+   cfg->device_id.device_id_hi = GET_HIGH_BYTE (APP_GSDML_DEVICE_ID);
+   cfg->device_id.device_id_lo = GET_LOW_BYTE (APP_GSDML_DEVICE_ID);
+
+   cfg->oem_device_id.vendor_id_hi = 0xc0;
+   cfg->oem_device_id.vendor_id_lo = 0xff;
+   cfg->oem_device_id.device_id_hi = 0xee;
+   cfg->oem_device_id.device_id_lo = 0x01;
+   strcpy (cfg->product_name, APP_GSDML_PRODUCT_NAME);
+
+   cfg->send_hello = true;
+
+   /* Timing */
+   cfg->min_device_interval = APP_GSDML_MIN_DEVICE_INTERVAL;
+
+   /* Should be set by application as part of network configuration. */
+   cfg->num_physical_ports = 1;
+
+   snprintf (
+      cfg->station_name,
+      sizeof (cfg->station_name),
+      APP_GSDML_DEFAULT_STATION_NAME);
+
+   /* Diagnosis mechanism */
+   /* Use "Extended channel diagnosis" instead of
+    * "Qualified channel diagnosis" format on the wire,
+    * as this is better supported by Wireshark.
+    */
+   cfg->use_qualified_diagnosis = false;
+
+   return 0;
+}
+
+int app_utils_get_netif_namelist (
+   const char * arg_str,
+   uint16_t max_port,
+   app_utils_netif_namelist_t * p_if_list,
+   uint16_t * p_num_ports)
+{
+   int ret = 0;
+   uint16_t i = 0;
+   uint16_t j = 0;
+   uint16_t if_index = 0;
+   uint16_t number_of_given_names = 1;
+   uint16_t if_list_size = max_port + 1;
+   char c;
+
+   if (max_port == 0)
+   {
+      printf ("Error: max_port is 0.\n");
+      return -1;
+   }
+
+   memset (p_if_list, 0, sizeof (*p_if_list));
+   c = arg_str[i++];
+   while (c != '\0')
+   {
+      if (c != ',')
+      {
+         if (if_index < if_list_size)
+         {
+            p_if_list->netif[if_index].name[j++] = c;
+         }
+      }
+      else
+      {
+         if (if_index < if_list_size)
+         {
+            p_if_list->netif[if_index].name[j++] = '\0';
+            j = 0;
+            if_index++;
+         }
+         number_of_given_names++;
+      }
+
+      c = arg_str[i++];
+   }
+
+   if (max_port == 1 && number_of_given_names > 1)
+   {
+      printf ("Error: Only 1 network interface expected as max_port is 1.\n");
+      return -1;
+   }
+   if (number_of_given_names == 2)
+   {
+      printf ("Error: It is illegal to give 2 interface names. Use 1, or one "
+              "more than the number of physical interfaces.\n");
+      return -1;
+   }
+   if (number_of_given_names > max_port + 1)
+   {
+      printf (
+         "Error: You have given %u interface names, but max is %u as "
+         "PNET_MAX_PHYSICAL_PORTS is %u.\n",
+         number_of_given_names,
+         max_port + 1,
+         max_port);
+      return -1;
+   }
+
+   if (number_of_given_names == 1)
+   {
+      if (strlen (p_if_list->netif[0].name) == 0)
+      {
+         printf ("Error: Zero length network interface name.\n");
+         return -1;
+      }
+      else
+      {
+         p_if_list->netif[1] = p_if_list->netif[0];
+         *p_num_ports = 1;
+      }
+   }
+   else
+   {
+      for (i = 0; i < number_of_given_names; i++)
+      {
+         if (strlen (p_if_list->netif[i].name) == 0)
+         {
+            printf ("Error: Zero length network interface name (%d).\n", i);
+            return -1;
+         }
+      }
+
+      *p_num_ports = number_of_given_names - 1;
+   }
+
+   return ret;
+}
+
+int app_utils_pnet_cfg_init_netifs (
+   const char * netif_list_str,
+   app_utils_netif_namelist_t * if_list,
+   uint16_t * number_of_ports,
+   pnet_if_cfg_t * if_cfg)
+{
+   int ret = 0;
+   int i = 0;
+   pnal_ipaddr_t ip;
+   pnal_ipaddr_t netmask;
+   pnal_ipaddr_t gateway;
+
+   ret = app_utils_get_netif_namelist (
+      netif_list_str,
+      PNET_MAX_PHYSICAL_PORTS,
+      if_list,
+      number_of_ports);
+   if (ret != 0)
+   {
+      return ret;
+   }
+   if_cfg->main_netif_name = if_list->netif[0].name;
+
+   for (i = 1; i <= *number_of_ports; i++)
+   {
+      if_cfg->physical_ports[i - 1].netif_name = if_list->netif[i].name;
+   }
+
+   /* Read IP, netmask, gateway from operating system */
+   ip = pnal_get_ip_address (if_cfg->main_netif_name);
+   netmask = pnal_get_netmask (if_cfg->main_netif_name);
+   gateway = pnal_get_gateway (if_cfg->main_netif_name);
+
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_addr, ip);
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_gateway, gateway);
+   app_utils_copy_ip_to_struct (&if_cfg->ip_cfg.ip_mask, netmask);
+
+   return ret;
+}
+
+static void app_utils_print_mac_address (const char * netif_name)
+{
+   pnal_ethaddr_t pnal_mac_addr;
+   if (pnal_get_macaddress (netif_name, &pnal_mac_addr) == 0)
+   {
+      APP_LOG_INFO (
+         "%02X:%02X:%02X:%02X:%02X:%02X\n",
+         pnal_mac_addr.addr[0],
+         pnal_mac_addr.addr[1],
+         pnal_mac_addr.addr[2],
+         pnal_mac_addr.addr[3],
+         pnal_mac_addr.addr[4],
+         pnal_mac_addr.addr[5]);
+   }
+   else
+   {
+      APP_LOG_ERROR ("Failed read mac address\n");
+   }
+}
+
+void app_utils_print_network_config (
+   pnet_if_cfg_t * if_cfg,
+   uint16_t number_of_ports)
+{
+   uint16_t i;
+   char hostname_string[PNAL_HOSTNAME_MAX_SIZE]; /* Terminated string */
+
+   APP_LOG_INFO ("Management port:      %s ", if_cfg->main_netif_name);
+   app_utils_print_mac_address (if_cfg->main_netif_name);
+   for (i = 1; i <= number_of_ports; i++)
+   {
+      APP_LOG_INFO (
+         "Physical port [%u]:    %s ",
+         i,
+         if_cfg->physical_ports[i - 1].netif_name);
+
+      app_utils_print_mac_address (if_cfg->physical_ports[i - 1].netif_name);
+   }
+
+   if (pnal_get_hostname (hostname_string) != 0)
+   {
+      hostname_string[0] = '\0';
+   }
+
+   APP_LOG_INFO ("Hostname:             %s\n", hostname_string);
+   APP_LOG_INFO (
+      "IP address:           %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_addr.a,
+      if_cfg->ip_cfg.ip_addr.b,
+      if_cfg->ip_cfg.ip_addr.c,
+      if_cfg->ip_cfg.ip_addr.d);
+   APP_LOG_INFO (
+      "Netmask:              %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_mask.a,
+      if_cfg->ip_cfg.ip_mask.b,
+      if_cfg->ip_cfg.ip_mask.c,
+      if_cfg->ip_cfg.ip_mask.d);
+   APP_LOG_INFO (
+      "Gateway:              %u.%u.%u.%u\n",
+      if_cfg->ip_cfg.ip_gateway.a,
+      if_cfg->ip_cfg.ip_gateway.b,
+      if_cfg->ip_cfg.ip_gateway.c,
+      if_cfg->ip_cfg.ip_gateway.d);
+}
+
+void app_utils_print_ioxs_change (
+   app_subslot_t * subslot,
+   const char * ioxs_str,
+   uint8_t iocs_current,
+   uint8_t iocs_new)
+{
+   if (iocs_current != iocs_new)
+   {
+      if (iocs_new == PNET_IOXS_BAD)
+      {
+         APP_LOG_WARNING (
+            "PLC reports %s BAD for slot %u subslot %u \"%s\"\n",
+            ioxs_str,
+            subslot->slot_nbr,
+            subslot->subslot_nbr,
+            subslot->submodule_name);
+      }
+      else if (iocs_new == PNET_IOXS_GOOD)
+      {
+         APP_LOG_DEBUG (
+            "PLC reports %s GOOD for slot %u subslot %u \"%s\".\n",
+            ioxs_str,
+            subslot->slot_nbr,
+            subslot->subslot_nbr,
+            subslot->submodule_name);
+      }
+      else if (iocs_new != PNET_IOXS_GOOD)
+      {
+         APP_LOG_WARNING (
+            "PLC reports %s %u for input slot %u subslot %u \"%s\".\n"
+            "  Is it in STOP mode?\n",
+            ioxs_str,
+            iocs_new,
+            subslot->slot_nbr,
+            subslot->subslot_nbr,
+            subslot->submodule_name);
+      }
+   }
+}
+
+int app_utils_plug_module (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint32_t id,
+   const char * name)
+{
+   if (slot_nbr >= PNET_MAX_SLOTS)
+   {
+      return -1;
+   }
+
+   p_api->slots[slot_nbr].module_id = id;
+   p_api->slots[slot_nbr].plugged = true;
+   p_api->slots[slot_nbr].name = name;
+
+   return 0;
+}
+
+int app_utils_pull_module (app_api_t * p_api, uint16_t slot_nbr)
+{
+   if (slot_nbr >= PNET_MAX_SLOTS)
+   {
+      return -1;
+   }
+
+   p_api->slots[slot_nbr].plugged = false;
+
+   return 0;
+}
+
+app_subslot_t * app_utils_plug_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint32_t submodule_ident,
+   const pnet_data_cfg_t * p_data_cfg,
+   const char * submodule_name,
+   app_utils_cyclic_callback cyclic_callback,
+   void * tag)
+{
+   uint16_t subslot_ix;
+
+   if (slot_nbr >= PNET_MAX_SLOTS || p_api == NULL || p_data_cfg == NULL)
+   {
+      return NULL;
+   }
+
+   for (subslot_ix = 0; subslot_ix < PNET_MAX_SUBSLOTS; subslot_ix++)
+   {
+      if (p_api->slots[slot_nbr].subslots[subslot_ix].used == false)
+      {
+         app_subslot_t * p_subslot =
+            &p_api->slots[slot_nbr].subslots[subslot_ix];
+
+         p_subslot->used = true;
+         p_subslot->plugged = true;
+         p_subslot->slot_nbr = slot_nbr;
+         p_subslot->subslot_nbr = subslot_nbr;
+         p_subslot->submodule_name = submodule_name;
+         p_subslot->submodule_id = submodule_ident;
+         p_subslot->data_cfg = *p_data_cfg;
+         p_subslot->cyclic_callback = cyclic_callback;
+         p_subslot->tag = tag;
+         p_subslot->iocs = 0;
+         return p_subslot;
+      }
+   }
+
+   return NULL;
+}
+
+int app_utils_pull_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr)
+{
+   app_subslot_t * p_subslot = NULL;
+
+   if (slot_nbr >= PNET_MAX_SUBSLOTS || p_api == NULL)
+   {
+      return -1;
+   }
+
+   p_subslot = app_utils_subslot_get (p_api, slot_nbr, subslot_nbr);
+   if (p_subslot != NULL)
+   {
+      memset (p_subslot, 0, sizeof (app_subslot_t));
+      p_subslot->used = false;
+      return 0;
+   }
+
+   return -1;
+}
+
+/**
+ * Get subslot application information.
+ * @param p_appdata        InOut: Application state.
+ * @param slot_nbr         In:    Slot number.
+ * @param subslot_nbr      In:    Subslot number. Range 0 - 0x9FFF.
+ * @return Reference to application subslot,
+ *         NULL if subslot is not found/plugged.
+ */
+app_subslot_t * app_utils_subslot_get (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr)
+{
+   uint16_t subslot_ix;
+
+   if (slot_nbr >= PNET_MAX_SLOTS || p_api == NULL)
+   {
+      return NULL;
+   }
+
+   for (subslot_ix = 0; subslot_ix < PNET_MAX_SUBSLOTS; subslot_ix++)
+   {
+      if (p_api->slots[slot_nbr].subslots[subslot_ix].subslot_nbr == subslot_nbr)
+      {
+         return &p_api->slots[slot_nbr].subslots[subslot_ix];
+      }
+   }
+
+   return NULL;
+}
+
+bool app_utils_subslot_is_input (const app_subslot_t * p_subslot)
+{
+   if (
+      p_subslot != NULL && (p_subslot->data_cfg.data_dir == PNET_DIR_INPUT ||
+                            p_subslot->data_cfg.data_dir == PNET_DIR_IO))
+   {
+      return true;
+   }
+
+   return false;
+}
+
+/**
+ * Return true if subslot is neither input or output.
+ *
+ * This is applies for DAP submodules/slots
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_no_io (const app_subslot_t * p_subslot)
+{
+   if (p_subslot != NULL && p_subslot->data_cfg.data_dir == PNET_DIR_NO_IO)
+   {
+      return true;
+   }
+
+   return false;
+}
+
+/**
+ * Return true if subslot is output.
+ *
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is output or input/output,
+ *         false if not.
+ */
+bool app_utils_subslot_is_output (const app_subslot_t * p_subslot)
+{
+   if (
+      p_subslot != NULL && (p_subslot->data_cfg.data_dir == PNET_DIR_OUTPUT ||
+                            p_subslot->data_cfg.data_dir == PNET_DIR_IO))
+   {
+      return true;
+   }
+
+   return false;
+}
+
+void app_utils_cyclic_data_poll (app_api_t * p_api)
+{
+   uint16_t slot;
+   uint16_t subslot;
+   app_subslot_t * p_subslot;
+
+   for (slot = 0; slot < PNET_MAX_SLOTS; slot++)
+   {
+      for (subslot = 0; subslot < PNET_MAX_SUBSLOTS; subslot++)
+      {
+         p_subslot = &p_api->slots[slot].subslots[subslot];
+         if (p_subslot->plugged && p_subslot->cyclic_callback != NULL)
+         {
+            p_subslot->cyclic_callback (p_subslot, p_subslot->tag);
+         }
+      }
+   }
+}

+ 372 - 0
sample_app/app_utils.h

@@ -0,0 +1,372 @@
+/*********************************************************************
+ *        _       _         _
+ *  _ __ | |_  _ | |  __ _ | |__   ___
+ * | '__|| __|(_)| | / _` || '_ \ / __|
+ * | |   | |_  _ | || (_| || |_) |\__ \
+ * |_|    \__|(_)|_| \__,_||_.__/ |___/
+ *
+ * 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 APP_UTILS_H
+#define APP_UTILS_H
+
+/**
+ * @file
+ * @brief Application utilities and helper functions
+ *
+ * Functions for getting string representation of
+ * P-Net events, error codes and more.
+ *
+ * API, slot and subslot administration.
+ *
+ * Initialization of P-Net configuration from app_gsdml.h.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "osal.h"
+#include "pnal.h"
+#include <pnet_api.h>
+
+typedef struct app_utils_netif_name
+{
+   char name[PNET_INTERFACE_NAME_MAX_SIZE];
+} app_utils_netif_name_t;
+
+typedef struct app_utils_netif_namelist
+{
+   app_utils_netif_name_t netif[PNET_MAX_PHYSICAL_PORTS + 1];
+} app_utils_netif_namelist_t;
+
+typedef struct app_subslot app_subslot_t;
+typedef void (*app_utils_cyclic_callback) (app_subslot_t * subslot, void * tag);
+
+/**
+ * Information of submodule plugged into a subslot.
+ * Note that submodule data is not stored here but must
+ * be handled by the submodule implementation.
+ * All parameters are initialized by the app_utils_plug_submodule()
+ * function.
+ * The cyclic_callback is generated when app_utils_cyclic_data_poll()
+ * is called. Typically on the tick event in the main task.
+ * tag parameter is passed with the cyclic_callback and
+ * is typically a handle to a submodule on application.
+ */
+typedef struct app_subslot
+{
+   bool used;
+   bool plugged;
+   uint16_t slot_nbr;
+   uint16_t subslot_nbr;
+   uint32_t submodule_id;
+   const char * submodule_name;
+   pnet_data_cfg_t data_cfg;
+   uint8_t iocs;
+   uint8_t iops;
+
+   app_utils_cyclic_callback cyclic_callback;
+   void * tag;
+} app_subslot_t;
+
+/**
+ * Information of module plugged into
+ * slot and array of subslots for admin
+ * of submodules.
+ */
+typedef struct app_slot
+{
+   bool plugged;
+   uint32_t module_id;
+   const char * name;
+   app_subslot_t subslots[PNET_MAX_SUBSLOTS];
+} app_slot_t;
+
+/**
+ * Profinet API state
+ * Used to admin modules is plugged into
+ * slots and submodules into subslots.
+ */
+typedef struct app_api_t
+{
+   uint32_t api_id;
+   uint32_t arep;
+   app_slot_t slots[PNET_MAX_SLOTS];
+} app_api_t;
+
+/**
+ * Convert IP address to string
+ * @param ip               In:    IP address
+ * @param outputstring     Out:   Resulting string buffer. Should have size
+ *                                PNAL_INET_ADDRSTR_SIZE.
+ */
+void app_utils_ip_to_string (pnal_ipaddr_t ip, char * outputstring);
+
+/**
+ * Get string description of data direction
+ * @param direction               In:    Submodule data direction
+ * @return String represention of data direction
+ */
+const char * app_utils_submod_dir_to_string (pnet_submodule_dir_t direction);
+
+/**
+ * Get string description of pnio produced or consumer status
+ * @param ioxs               In:    Producer or consumer status (iops/iocs)
+ * @return String represention of ioxs (iops/iocs)
+ */
+const char * app_utils_ioxs_to_string (pnet_ioxs_values_t ioxs);
+
+/**
+ * Convert MAC address to string
+ * @param mac              In:    MAC address
+ * @param outputstring     Out:   Resulting string buffer. Should have size
+ *                                PNAL_ETH_ADDRSTR_SIZE.
+ */
+void app_utils_mac_to_string (pnet_ethaddr_t mac, char * outputstring);
+
+/**
+ * Convert error code to string format
+ * Only common error codes supported.
+ * Todo: Add rest of error codes.
+ *
+ * @param err_cls        In:   The error class. See PNET_ERROR_CODE_1_*
+ * @param err_code       In:   The error code. See PNET_ERROR_CODE_2_*
+ * @param err_cls_str    Out:   The error class string
+ * @param err_code_str   Out:   The error code string
+ */
+void app_utils_get_error_code_strings (
+   uint16_t err_cls,
+   uint16_t err_code,
+   const char ** err_cls_str,
+   const char ** err_code_str);
+
+/**
+ * Copy an IP address (as an integer) to a struct
+ * @param destination_struct  Out:   Destination
+ * @param ip                  In:    IP address
+ */
+void app_utils_copy_ip_to_struct (
+   pnet_cfg_ip_addr_t * destination_struct,
+   pnal_ipaddr_t ip);
+
+/**
+ * Return a string representation of
+ * the given dcontrol command.
+ * @param event            In:    control_command
+ * @return  A string representing the command
+ */
+const char * app_utils_dcontrol_cmd_to_string (
+   pnet_control_command_t control_command);
+
+/**
+ * Return a string representation of the given event.
+ * @param event            In:    event
+ * @return  A string representing the event
+ */
+const char * app_utils_event_to_string (pnet_event_values_t event);
+
+/**
+ * Get network configuration from a string
+ * defining a list of network interfaces examples:
+ * "eth0" or "br0,eth0,eth1"
+ * @param netif_list_str      In: list of network ifs
+ * @param if_list             Out: Array of network ifs
+ * @param number_of_ports     Out: Number of ports
+ * @param if_cfg              Out: P-Net network configuration
+ * @return 0 on success, -1 on error
+ */
+int app_utils_pnet_cfg_init_netifs (
+   const char * netif_list_str,
+   app_utils_netif_namelist_t * if_list,
+   uint16_t * number_of_ports,
+   pnet_if_cfg_t * if_cfg);
+
+/**
+ * Parse a comma separated list of network interfaces and check
+ * that the number of interfaces match the PNET_MAX_PHYSICAL_PORTS
+ * configuration.
+ *
+ * For a single Ethernet interface, the \a arg_str should consist of
+ * one name. For two Ethernet interfaces, the  \a arg_str should consist of
+ * three names, as we also need a bridge interface.
+ *
+ * Does only consider the number of commaseparated names. No check of the
+ * names themselves are done.
+ *
+ * Examples:
+ * arg_str                 num_ports
+ * "eth0"                  1
+ * "eth0,eth1"             error (We need a bridge as well)
+ * "br0,eth0,eth1"         2
+ *
+ * @param arg_str      In:   Network interface list as comma separated,
+ *                           terminated string. For example "eth0" or
+ *                           "br0,eth0,eth1".
+ * @param max_port     In:   PNET_MAX_PHYSICAL_PORTS, passed as argument to
+ *                           allow test.
+ * @param p_if_list    Out:  List of network interfaces
+ * @param p_num_ports  Out:  Resulting number of physical ports
+ * @return  0  on success
+ *         -1  on error
+ */
+int app_utils_get_netif_namelist (
+   const char * arg_str,
+   uint16_t max_port,
+   app_utils_netif_namelist_t * p_if_list,
+   uint16_t * p_num_ports);
+
+/**
+ * Print network configuration using APP_LOG_INFO().
+ * @param if_cfg           In:   Network configuration
+ * @param number_of_ports  In:   Number of used ports
+ */
+void app_utils_print_network_config (
+   pnet_if_cfg_t * if_cfg,
+   uint16_t number_of_ports);
+
+/**
+ * Print iocs warning message if ioxs is changed.
+ * @param subslot          In: Subslot
+ * @param ioxs_str         In: String decription Producer or Consumer
+ * @param ioxs_current     In: Current status
+ * @param ioxs_new         In: New status
+ */
+void app_utils_print_ioxs_change (
+   app_subslot_t * subslot,
+   const char * ioxs_str,
+   uint8_t ioxs_current,
+   uint8_t ioxs_new);
+
+/**
+ * Init the p-net configuration to default values.
+ * Some values hardcoded, some picked from app_gsdlm.h
+ * Network configuration not initialized.
+ * This means'.if_cfg' must be set by application.
+ * Use this function to init P-Net configuration before
+ * before passing config to app_init().
+ * @param pnet_cfg     Out:   Configuration for use by p-net
+ * @return  0  if the operation succeeded.
+ *          -1 if an error occurred.
+ */
+int app_utils_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg);
+
+/**
+ * Plug application module.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param module_id        In:    Module identity
+ * @return 0 on success, -1 on error
+ */
+int app_utils_plug_module (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint32_t id,
+   const char * name);
+
+/**
+ * Pull any application module in given slot.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @return 0 on success, -1 on error
+ */
+int app_utils_pull_module (app_api_t * p_api, uint16_t slot_nbr);
+
+/**
+ * Plug application submodule.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param subslot_nbr      In:    Subslot number
+ * @param submodule_id     In:    Submodule identity
+ * @param p_data_cfg       In:    Data configuration,
+ *                                direction, in and out sizes
+ * @param submodule_name   In:    Submodule name
+ * @param cyclic_callback  In:    Submodule data callback
+ * @param tag              In:    Tag passed in cyclic callback
+ *                                Typicall application or
+ *                                submodule handle
+ *
+ * @return Reference to allocated subslot,
+ *         NULL if no free subslot is available. This should
+ *         never happen if application is aligned with p-net state.
+ */
+app_subslot_t * app_utils_plug_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr,
+   uint32_t submodule_id,
+   const pnet_data_cfg_t * p_data_cfg,
+   const char * submodule_name,
+   app_utils_cyclic_callback cyclic_callback,
+   void * tag);
+
+/**
+ * Unplug any application submodule from given subslot.
+ * @param p_api            InOut: API
+ * @param slot_nbr         In:    Slot number
+ * @param subslot_nbr      In:    Subslot number
+ * @return 0 on success, -1 on error.
+ */
+int app_utils_pull_submodule (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr);
+
+/**
+ * Generate data callback for all plugged
+ * submodules. The callback passsed in
+ * app_utils_plug_submodule is used.
+ * @param p_api         In:   API
+ */
+void app_utils_cyclic_data_poll (app_api_t * p_api);
+
+/**
+ * Get subslot application information.
+ *
+ * @param p_appdata        InOut: Application state.
+ * @param slot_nbr         In:    Slot number.
+ * @param subslot_nbr      In:    Subslot number. Range 0 - 0x9FFF.
+ * @return Reference to application subslot,
+ *         NULL if subslot is not found/plugged.
+ */
+app_subslot_t * app_utils_subslot_get (
+   app_api_t * p_api,
+   uint16_t slot_nbr,
+   uint16_t subslot_nbr);
+
+/**
+ * Return true if subslot is input.
+ * @param p_subslot        In:    Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_input (const app_subslot_t * p_subslot);
+
+/**
+ * Return true if subslot is neither input or output.
+ * This is applies for DAP submodules/slots
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is input or input/output.
+ *         false if not.
+ */
+bool app_utils_subslot_is_no_io (const app_subslot_t * p_subslot);
+
+/**
+ * Return true if subslot is output.
+ * @param p_subslot     In: Reference to subslot.
+ * @return true if subslot is output or input/output,
+ *         false if not.
+ */
+bool app_utils_subslot_is_output (const app_subslot_t * p_subslot);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_UTILS_H */

Різницю між файлами не показано, бо вона завелика
+ 234 - 633
sample_app/sampleapp_common.c


+ 59 - 339
sample_app/sampleapp_common.h

@@ -24,165 +24,22 @@
 extern "C" {
 #endif
 
-#define CHANNEL_ERRORTYPE_SHORT_CIRCUIT                       0x0001
-#define CHANNEL_ERRORTYPE_LINE_BREAK                          0x0006
-#define CHANNEL_ERRORTYPE_DATA_TRANSMISSION_IMPOSSIBLE        0x8000
-#define CHANNEL_ERRORTYPE_NETWORK_COMPONENT_FUNCTION_MISMATCH 0x8008
-#define EXTENDED_CHANNEL_ERRORTYPE_FRAME_DROPPED              0x8000
-#define EXTENDED_CHANNEL_ERRORTYPE_MAUTYPE_MISMATCH           0x8001
-#define EXTENDED_CHANNEL_ERRORTYPE_LINE_DELAY_MISMATCH        0x8002
+#define APP_TICK_INTERVAL_US 1000 /* 1 ms */
 
-/********************** Settings **********************************************/
+/* Thread configuration for targets where sample
+ * event loop is run in a separate thread (not main).
+ * This applies for linux sample app implementation.
+ */
+#define APP_MAIN_THREAD_PRIORITY  15
+#define APP_MAIN_THREAD_STACKSIZE 4096 /* bytes */
 
 #define APP_DATA_LED_ID            1
 #define APP_PROFINET_SIGNAL_LED_ID 2
-#define APP_EVENT_READY_FOR_DATA   BIT (0)
-#define APP_EVENT_TIMER            BIT (1)
-#define APP_EVENT_ALARM            BIT (2)
-#define APP_EVENT_ABORT            BIT (15)
-
-#define APP_TICK_INTERVAL_US                1000 /* 1 ms */
-#define APP_ALARM_USI                       0x0010
-#define APP_DIAG_CHANNEL_NUMBER             4
-#define APP_DIAG_CHANNEL_DIRECTION          PNET_DIAG_CH_PROP_DIR_INPUT
-#define APP_DIAG_CHANNEL_NUMBER_OF_BITS     PNET_DIAG_CH_PROP_TYPE_1_BIT
-#define APP_DIAG_CHANNEL_SEVERITY           PNET_DIAG_CH_PROP_MAINT_FAULT
-#define APP_DIAG_CHANNEL_ERRORTYPE          CHANNEL_ERRORTYPE_SHORT_CIRCUIT
-#define APP_DIAG_CHANNEL_ADDVALUE_A         0
-#define APP_DIAG_CHANNEL_ADDVALUE_B         1234
-#define APP_DIAG_CHANNEL_EXTENDED_ERRORTYPE 0
-#define APP_DIAG_CHANNEL_QUAL_SEVERITY      0 /* Not used (Max one bit set) */
 
 #define APP_TICKS_READ_BUTTONS 10
 #define APP_TICKS_UPDATE_DATA  100
 
-/**************** From the GSDML file ****************************************/
-
-#define APP_DEFAULT_STATION_NAME "rt-labs-dev"
-#define APP_PARAM_IDX_1          123
-#define APP_PARAM_IDX_2          124
-#define APP_API                  0
-
-#define APP_DIAG_CUSTOM_USI 0x1234
-
-/* See "Specification for GSDML" 8.26 LogBookEntryItem for allowed values */
-#define APP_LOGBOOK_ERROR_CODE   0x20 /* Manufacturer specific */
-#define APP_LOGBOOK_ERROR_DECODE 0x82 /* Manufacturer specific */
-#define APP_LOGBOOK_ERROR_CODE_1 PNET_ERROR_CODE_1_FSPM
-#define APP_LOGBOOK_ERROR_CODE_2 0x00       /* Manufacturer specific */
-#define APP_LOGBOOK_ENTRY_DETAIL 0xFEE1DEAD /* Manufacturer specific */
-
-/*
- * I/O Modules. These modules and their sub-modules must be plugged by the
- * application after the call to pnet_init.
- *
- * Assume that all modules only have a single submodule, with same number.
- */
-#define APP_MOD_8_0_IDENT       0x00000030 /* 8 bit input */
-#define APP_MOD_0_8_IDENT       0x00000031 /* 8 bit output */
-#define APP_MOD_8_8_IDENT       0x00000032 /* 8 bit input, 8 bit output */
-#define APP_SUBMOD_CUSTOM_IDENT 0x00000001
-
-#define APP_SUBSLOT_CUSTOM 0x00000001
-
-#define APP_DATASIZE_INPUT     1 /* bytes, for digital inputs data */
-#define APP_DATASIZE_OUTPUT    1 /* bytes, for digital outputs data */
-#define APP_ALARM_PAYLOAD_SIZE 1 /* bytes */
-
-/*** Example on how to keep lists of supported modules and submodules ********/
-
-static const uint32_t cfg_available_module_types[] = {
-   PNET_MOD_DAP_IDENT,
-   APP_MOD_8_0_IDENT,
-   APP_MOD_0_8_IDENT,
-   APP_MOD_8_8_IDENT};
-
-typedef struct cfg_submodule_type
-{
-   const char * name;
-   uint32_t api;
-   uint32_t module_ident_nbr;
-   uint32_t submodule_ident_nbr;
-   pnet_submodule_dir_t data_dir;
-   uint16_t insize;  /* bytes */
-   uint16_t outsize; /* bytes */
-} cfg_submodule_type_t;
-
-static const cfg_submodule_type_t cfg_available_submodule_types[] = {
-   {"DAP Identity 1",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-   {"DAP Interface 1",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-   {"DAP Port 1",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_INTERFACE_1_PORT_1_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-#if PNET_MAX_PHYSICAL_PORTS >= 2
-   {"DAP Port 2",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_INTERFACE_1_PORT_2_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-#endif
-#if PNET_MAX_PHYSICAL_PORTS >= 3
-   {"DAP Port 3",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_INTERFACE_1_PORT_3_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-#endif
-#if PNET_MAX_PHYSICAL_PORTS >= 4
-   {"DAP Port 4",
-    APP_API,
-    PNET_MOD_DAP_IDENT,
-    PNET_SUBMOD_DAP_INTERFACE_1_PORT_4_IDENT,
-    PNET_DIR_NO_IO,
-    0,
-    0},
-#endif
-   {"Input 8 bits",
-    APP_API,
-    APP_MOD_8_0_IDENT,
-    APP_SUBMOD_CUSTOM_IDENT,
-    PNET_DIR_INPUT,
-    APP_DATASIZE_INPUT,
-    0},
-   {"Output 8 bits",
-    APP_API,
-    APP_MOD_0_8_IDENT,
-    APP_SUBMOD_CUSTOM_IDENT,
-    PNET_DIR_OUTPUT,
-    0,
-    APP_DATASIZE_OUTPUT},
-   {"Input 8 bits output 8 bits",
-    APP_API,
-    APP_MOD_8_8_IDENT,
-    APP_SUBMOD_CUSTOM_IDENT,
-    PNET_DIR_IO,
-    APP_DATASIZE_INPUT,
-    APP_DATASIZE_OUTPUT},
-};
-
-/************************ App data storage ***********************************/
-
-struct cmd_args
+typedef struct app_args
 {
    char path_button1[PNET_MAX_FILE_FULLPATH_SIZE]; /** Terminated string */
    char path_button2[PNET_MAX_FILE_FULLPATH_SIZE]; /** Terminated string */
@@ -195,224 +52,87 @@ struct cmd_args
    int show;
    bool factory_reset;
    bool remove_files;
-};
-
-typedef struct app_netif_name
-{
-   char name[PNET_INTERFACE_NAME_MAX_SIZE];
-} app_netif_name_t;
-
-typedef struct app_netif_namelist
-{
-   app_netif_name_t netif[PNET_MAX_PHYSICAL_PORTS + 1];
-} app_netif_namelist_t;
+} app_args_t;
 
-typedef struct app_subslot
+typedef enum
 {
-   bool used;
-   bool plugged;
-   uint16_t slot_nbr;
-   uint16_t subslot_nbr;
-   uint32_t submodule_id;
-   const char * submodule_name;
-   pnet_data_cfg_t data_cfg;
-   uint8_t * p_in_data;
-} app_subslot_t;
+   RUN_IN_SEPARATE_THREAD,
+   RUN_IN_MAIN_THREAD
+} app_run_in_separate_task_t;
 
-typedef struct app_slot
-{
-   bool plugged;
-   uint32_t module_id;
-   app_subslot_t subslots[PNET_MAX_SUBSLOTS];
-} app_slot_t;
+typedef struct app_data_t app_data_t;
 
-typedef struct app_api_t
-{
-   uint32_t api_id;
-   uint32_t arep;
-   app_slot_t slots[PNET_MAX_SLOTS];
-} app_api_t;
-
-typedef struct app_data_obj
-{
-   os_timer_t * main_timer;
-   os_event_t * main_events;
-   app_api_t main_api;
-   bool alarm_allowed;
-   pnet_alarm_argument_t alarm_arg;
-   struct cmd_args arguments;
-   app_netif_namelist_t if_list;
-   uint32_t app_param_1;
-   uint32_t app_param_2;
-   uint8_t inputdata[APP_DATASIZE_INPUT];
-   uint32_t arep_for_appl_ready;
-} app_data_t;
-
-typedef struct app_data_and_stack_obj
-{
-   app_data_t * appdata;
-   pnet_t * net;
-} app_data_and_stack_t;
-
-typedef enum app_demo_state
-{
-   APP_DEMO_STATE_ALARM_SEND,
-   APP_DEMO_STATE_LOGBOOK_ENTRY,
-   APP_DEMO_STATE_ABORT_AR,
-   APP_DEMO_STATE_CYCLIC_REDUNDANT,
-   APP_DEMO_STATE_CYCLIC_NORMAL,
-   APP_DEMO_STATE_DIAG_STD_ADD,
-   APP_DEMO_STATE_DIAG_STD_UPDATE,
-   APP_DEMO_STATE_DIAG_STD_REMOVE,
-   APP_DEMO_STATE_DIAG_USI_ADD,
-   APP_DEMO_STATE_DIAG_USI_UPDATE,
-   APP_DEMO_STATE_DIAG_USI_REMOVE,
-} app_demo_state_t;
-
-/********************* Helper function declarations ***************************/
-
-/**
- * Convert MAC address to string
- * @param mac              In:    MAC address
- * @param outputstring     Out:   Resulting string buffer. Should have size
- *                                PNAL_ETH_ADDRSTR_SIZE.
- */
-void app_mac_to_string (pnet_ethaddr_t mac, char * outputstring);
+void app_pnet_cfg_init_default (pnet_cfg_t * pnet_cfg);
 
 /**
- * Print out current IP address, netmask and default gateway.
- *
- * @param ip               In:    IP address
- * @param netmask          In:    Netmask
- * @param gateway          In:    Gateway
- * @return  0  if the operation succeeded.
- *          -1 if an error occurred.
+ * Initialize application
+ *
+ * Initialize P-Net stack and application.
+ * The pnet_cfg argument shall have been initialized using
+ * app_pnet_cfg_init_default() before this function is
+ * called.
+ * @param pnet_cfg               In:    P-Net start configuration
+ * @return Application handle, NULL on error
  */
-void app_print_network_details (
-   pnal_ipaddr_t ip,
-   pnal_ipaddr_t netmask,
-   pnal_ipaddr_t gateway);
+app_data_t * app_init (pnet_cfg_t * pnet_cfg);
 
 /**
- * Adjust some members of the p-net configuration object.
- *
- * @param stack_config     Out:   Configuration for use by p-net
- * @return  0  if the operation succeeded.
- *          -1 if an error occurred.
+ * Start application
+ *
+ * Application must have been initialized using app_init() before
+ * app_start() is called.
+ * If task_config parameters is set to RUN_IN_SEPARATE_THREAD a
+ * thread execution the app_loop_forever() function is started.
+ * If task_config is set to RUN_IN_MAIN_THREAD no such thread is
+ * started and the caller must call the app_loop_forever() after
+ * calling this function.
+ * RUN_IN_MAIN_THREAD is intended for rt-kernel targets.
+ * RUN_IN_SEPARATE_THREAD is intended for linux targets.
+ * @param app                 In:    Application handle
+ * @param task_config         In:    Defines if stack and application
+ *                                   is run in main or separate task.
+ *
+ * @return 0 on success, -1 on error
  */
-int app_pnet_cfg_init_default (pnet_cfg_t * stack_config);
+int app_start (app_data_t * app, app_run_in_separate_task_t task_config);
 
 /**
- * Initialize interface configuration from argument string.
- *
- * @param netif_list_str   In:    Comma separated list of network interfaces.
- *                                Terminated string.
- * @param if_list          Out:   Network interface string storage.
- * @param p_cfg            InOut: p-net configuration
- * @param verbosity        In:    Verbosity
- * @return  0  on success
- *         -1  on error
+ * Application task definition. Handles events
+ * in eternal loop.
+ * @param arg                 In: Application handle
+ * return Should not
  */
-int app_pnet_cfg_init_netifs (
-   const char * netif_list_str,
-   app_netif_namelist_t * if_list,
-   uint16_t * p_number_of_ports,
-   pnet_cfg_t * p_cfg,
-   int verbosity);
+void app_loop_forever (void * arg);
 
 /**
- * Parse a comma separated list of network interfaces and check
- * that the number of interfaces match the PNET_MAX_PHYSICAL_PORTS
- * configuration.
+ * Get P-Net instance from application
  *
- * For a single Ethernet interface, the \a arg_str should consist of
- * one name. For two Ethernet interfaces, the  \a arg_str should consist of
- * three names, as we also need a bridge interface.
+ * @param app                 In:    Application handle
  *
- * Does only consider the number of commaseparated names. No check of the
- * names themselves are done.
- *
- * Examples:
- * arg_str                 num_ports
- * "eth0"                  1
- * "eth0,eth1"             error (We need a bridge as well)
- * "br0,eth0,eth1"         2
- *
- * @param arg_str      In:   Network interface list as comma separated,
- *                           terminated string. For example "eth0" or
- *                           "br0,eth0,eth1".
- * @param max_port     In:   PNET_MAX_PHYSICAL_PORTS, passed as argument to
- *                           allow test.
- * @param p_if_list    Out:  List of network interfaces
- * @param p_num_ports  Out:  Resulting number of physical ports
- * @return  0  on success
- *         -1  on error
+ * @return P-Net instance
  */
-int app_get_netif_namelist (
-   const char * arg_str,
-   uint16_t max_port,
-   app_netif_namelist_t * p_if_list,
-   uint16_t * p_num_ports);
+pnet_t * app_get_pnet_instance (app_data_t * app);
 
 /**
- * Plug DAP (sub-)modules. This operation shall be called after p-net
- * stack initialization
- *
- * @param net              InOut: p-net stack instance
- * @param arg              In:    user data for callbacks
- */
-void app_plug_dap (pnet_t * net, void * arg);
-
-/**
- * Copy an IP address (as an integer) to a struct
- *
- * @param destination_struct  Out:   Destination
- * @param ip                  In:    IP address
- */
-void app_copy_ip_to_struct (
-   pnet_cfg_ip_addr_t * destination_struct,
-   pnal_ipaddr_t ip);
-
-/**
- * Sample app main loop.
- *
- * @param net              InOut: p-net stack instance
- * @param p_appdata        In:    Appdata
- */
-void app_loop_forever (pnet_t * net, app_data_t * p_appdata);
-
-/********************** Hardware interaction **********************************/
-
-/**
- * Control a LED
- *
- * This is hardware dependent, so the implementation of this function should be
- * located in the corresponding main file.
+ * Set LED state
+ * Hardware specific. Implemented in sample app main file for
+ * each supported platform.
  *
  * @param id               In:    LED number, starting from 0.
  * @param led_state        In:    LED state. Use true for on and false for off.
- * @param verbosity        In:    Print new led_state, if verbosity > 0
- * @return  0  if the operation succeeded.
- *          -1 if an error occurred.
  */
-int app_set_led (uint16_t id, bool led_state, int verbosity);
+void app_set_led (uint16_t id, bool led_state);
 
 /**
- * Read a button.
+ * Read button state
  *
- * This is hardware dependent, so the implementation of this function should be
- * located in the corresponding main file.
+ * Hardware specific. Implemented in sample app main file for
+ * each supported platform.
  *
- * @param p_appdata        In:    App data
  * @param id               In:    Button number, starting from 0.
- * @param p_pressed        Out:   Button state. Use true for pressed.
- * @return  0  if the operation succeeded.
- *          -1 if an error occurred.
+ * @return  true if button is pressed, false if not
  */
-void app_get_button (
-   const app_data_t * p_appdata,
-   uint16_t id,
-   bool * p_pressed);
+bool app_get_button (uint16_t id);
 
 #ifdef __cplusplus
 }

+ 93 - 117
src/ports/linux/sampleapp_main.c

@@ -16,6 +16,9 @@
 #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 */
@@ -32,28 +35,26 @@
 #include <string.h>
 #include <unistd.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_MAIN_THREAD_PRIORITY  15
-#define APP_MAIN_THREAD_STACKSIZE 4096 /* bytes */
 #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 */
+
 /* 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 */
 
-/************************* Utilities ******************************************/
+app_args_t app_args = {0};
 
-static void main_timer_tick (os_timer_t * timer, void * arg)
-{
-   app_data_t * p_appdata = (app_data_t *)arg;
-
-   os_event_set (p_appdata->main_events, APP_EVENT_TIMER);
-}
+/************************* Utilities ******************************************/
 
 void show_usage()
 {
@@ -95,7 +96,7 @@ void show_usage()
       APP_DEFAULT_ETHERNET_INTERFACE);
    printf (
       "   -s NAME      Set station name. Defaults to %s  Only used\n",
-      APP_DEFAULT_STATION_NAME);
+      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");
@@ -114,8 +115,11 @@ void show_usage()
  * @param argv      In: Arguments
  * @return Parsed arguments
  */
-struct cmd_args parse_commandline_arguments (int argc, char * argv[])
+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)
    {
@@ -127,18 +131,16 @@ struct cmd_args parse_commandline_arguments (int argc, char * argv[])
    }
 
    /* Default values */
-   struct cmd_args output_arguments;
    strcpy (output_arguments.path_button1, "");
    strcpy (output_arguments.path_button2, "");
    strcpy (output_arguments.path_storage_directory, "");
-   strcpy (output_arguments.station_name, APP_DEFAULT_STATION_NAME);
+   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;
 
-   int option;
    while ((option = getopt (argc, argv, "hvgfri:s:b:d:p:")) != -1)
    {
       switch (option)
@@ -246,29 +248,26 @@ bool read_bool_from_file (const char * filepath)
    return ch == '1';
 }
 
-void app_get_button (const app_data_t * p_appdata, uint16_t id, bool * p_pressed)
+bool app_get_button (uint16_t id)
 {
    if (id == 0)
    {
-      if (p_appdata->arguments.path_button1[0] != '\0')
+      if (app_args.path_button1[0] != '\0')
       {
-         *p_pressed = read_bool_from_file (p_appdata->arguments.path_button1);
+         return read_bool_from_file (app_args.path_button1);
       }
    }
    else if (id == 1)
    {
-      if (p_appdata->arguments.path_button2[0] != '\0')
+      if (app_args.path_button2[0] != '\0')
       {
-         *p_pressed = read_bool_from_file (p_appdata->arguments.path_button2);
+         return read_bool_from_file (app_args.path_button2);
       }
    }
-   else
-   {
-      *p_pressed = false;
-   }
+   return false;
 }
 
-int app_set_led (uint16_t id, bool led_state, int verbosity)
+void app_set_led (uint16_t id, bool led_state)
 {
    char id_str[7] = {0}; /** Terminated string */
    const char * argv[4];
@@ -284,34 +283,10 @@ int app_set_led (uint16_t id, bool led_state, int verbosity)
    if (pnal_execute_script (argv) != 0)
    {
       printf ("Failed to set LED state\n");
-      return -1;
    }
-   return 0;
-}
-
-/**
- * Run main loop in separate thread
- *
- * @param arg              In:    Thread argument   app_data_and_stack_t
- */
-void pn_main_thread (void * arg)
-{
-   pnet_t * net;
-   app_data_t * p_appdata;
-   app_data_and_stack_t * appdata_and_stack;
-
-   appdata_and_stack = (app_data_and_stack_t *)arg;
-   p_appdata = appdata_and_stack->appdata;
-   net = appdata_and_stack->net;
-
-   app_loop_forever (net, p_appdata);
-
-   os_timer_destroy (p_appdata->main_timer);
-   os_event_destroy (p_appdata->main_events);
-   printf ("Quitting the sample application\n\n");
 }
 
-int app_pnet_cfg_init_storage (pnet_cfg_t * p_cfg, struct cmd_args * p_args)
+int app_pnet_cfg_init_storage (pnet_cfg_t * p_cfg, app_args_t * p_args)
 {
    strcpy (p_cfg->file_directory, p_args->path_storage_directory);
 
@@ -357,124 +332,125 @@ int app_pnet_cfg_init_storage (pnet_cfg_t * p_cfg, struct cmd_args * p_args)
 
 int main (int argc, char * argv[])
 {
-   pnet_t * net;
-   pnet_cfg_t pnet_default_cfg;
-   app_data_and_stack_t appdata_and_stack;
-   app_data_t appdata;
-   int ret = 0;
-   uint16_t number_of_ports = 0;
-
-   memset (&appdata, 0, sizeof (appdata));
-   appdata.alarm_allowed = true;
+   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 */
-   appdata.arguments = parse_commandline_arguments (argc, argv);
+   app_args = parse_commandline_arguments (argc, argv);
 
-   printf ("\n** Starting Profinet sample application " PNET_VERSION " **\n");
-   if (appdata.arguments.verbosity > 0)
-   {
-      printf (
-         "Number of slots:      %u (incl slot for DAP module)\n",
-         PNET_MAX_SLOTS);
-      printf ("P-net log level:      %u (DEBUG=0, FATAL=4)\n", LOG_LEVEL);
-      printf ("App verbosity level:  %u\n", appdata.arguments.verbosity);
-      printf ("Max number of ports:  %u\n", PNET_MAX_PHYSICAL_PORTS);
-      printf ("Network interfaces:   %s\n", appdata.arguments.eth_interfaces);
-      printf ("Button1 file:         %s\n", appdata.arguments.path_button1);
-      printf ("Button2 file:         %s\n", appdata.arguments.path_button2);
-      printf ("Station name:         %s\n", appdata.arguments.station_name);
-   }
+   app_log_level = APP_LOG_LEVEL_FATAL - app_args.verbosity;
+   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);
+
+   app_pnet_cfg_init_default (&pnet_cfg);
 
-   app_pnet_cfg_init_default (&pnet_default_cfg);
-   pnet_default_cfg.cb_arg = (void *)&appdata;
-   strcpy (pnet_default_cfg.station_name, appdata.arguments.station_name);
-   pnet_default_cfg.pnal_cfg.snmp_thread.prio = APP_SNMP_THREAD_PRIORITY;
-   pnet_default_cfg.pnal_cfg.snmp_thread.stack_size = APP_SNMP_THREAD_STACKSIZE;
-   pnet_default_cfg.pnal_cfg.eth_recv_thread.prio = APP_ETH_THREAD_PRIORITY;
-   pnet_default_cfg.pnal_cfg.eth_recv_thread.stack_size =
-      APP_ETH_THREAD_STACKSIZE;
-
-   ret = app_pnet_cfg_init_netifs (
-      appdata.arguments.eth_interfaces,
-      &appdata.if_list,
+   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,
-      &pnet_default_cfg,
-      appdata.arguments.verbosity);
+      &netif_cfg);
    if (ret != 0)
    {
       exit (EXIT_FAILURE);
    }
-   pnet_default_cfg.num_physical_ports = number_of_ports;
 
-   ret = app_pnet_cfg_init_storage (&pnet_default_cfg, &appdata.arguments);
+   pnet_cfg.if_cfg = netif_cfg;
+   pnet_cfg.num_physical_ports = number_of_ports;
+
+   app_utils_print_network_config (&netif_cfg, number_of_ports);
+
+   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;
+
+   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 (appdata.arguments.remove_files == true)
+   if (app_args.remove_files == true)
    {
       printf ("\nRemoving stored files\n");
-      (void)pnet_remove_data_files (pnet_default_cfg.file_directory);
+      printf ("Exit application\n");
+      (void)pnet_remove_data_files (pnet_cfg.file_directory);
       exit (EXIT_SUCCESS);
    }
 
-   /* Initialize profinet stack */
-   net = pnet_init (&pnet_default_cfg);
-   if (net == NULL)
+   APP_LOG_INFO ("Init sample application\n");
+   sample_app = app_init (&pnet_cfg);
+   if (sample_app == NULL)
    {
-      printf ("Failed to initialize p-net. Do you have enough Ethernet "
-              "interface permission?\n");
+      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 (appdata.arguments.factory_reset == true)
+   if (app_args.factory_reset == true)
    {
       printf ("\nPerforming factory reset\n");
-      (void)pnet_factory_reset (net);
+      printf ("Exit application\n");
+      (void)pnet_factory_reset (app_get_pnet_instance (sample_app));
       exit (EXIT_SUCCESS);
    }
 
    /* Show stack info and exit */
-   if (appdata.arguments.show > 0)
+   if (app_args.show > 0)
    {
       int level = 0xFFFF;
 
       printf ("\nShowing stack information.\n\n");
-      if (appdata.arguments.show == 1)
+      if (app_args.show == 1)
       {
          level = 0x2010; /* See documentation for pnet_show() */
       }
 
-      pnet_show (net, level);
+      pnet_show (app_get_pnet_instance (sample_app), level);
+      printf ("Exit application\n");
       exit (EXIT_SUCCESS);
    }
 
-   /* Start thread and timer */
-   appdata_and_stack.appdata = &appdata;
-   appdata_and_stack.net = net;
-   appdata.main_events = os_event_create();
-   appdata.main_timer = os_timer_create (
-      APP_TICK_INTERVAL_US,
-      main_timer_tick,
-      (void *)&appdata,
-      false);
-   os_thread_create (
-      "pn_main_thread",
-      APP_MAIN_THREAD_PRIORITY,
-      APP_MAIN_THREAD_STACKSIZE,
-      pn_main_thread,
-      (void *)&appdata_and_stack);
-   os_timer_start (appdata.main_timer);
+   APP_LOG_INFO ("Start sample application\n");
+   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;
 }

+ 75 - 75
src/ports/rt-kernel/sampleapp_main.c

@@ -14,6 +14,9 @@
  ********************************************************************/
 
 #include "sampleapp_common.h"
+#include "app_utils.h"
+#include "app_log.h"
+#include "app_gsdml.h"
 
 #include "osal_log.h"
 #include "osal.h"
@@ -32,16 +35,17 @@
 #define GPIO_BUTTON2                   GPIO_P15_12
 #define APP_DEFAULT_ETHERNET_INTERFACE "en1"
 #define APP_DEFAULT_FILE_DIRECTORY     "/disk1"
+#define APP_LOG_LEVEL                  APP_LOG_LEVEL_DEBUG
 
 /********************************** Globals ***********************************/
 
-static app_data_t * gp_appdata = NULL;
-static pnet_t * g_net = NULL;
-static pnet_cfg_t pnet_default_cfg;
+static app_data_t * sample_app = NULL;
+static pnet_cfg_t pnet_cfg = {0};
+app_args_t app_args = {0};
 
 /************************* Utilities ******************************************/
 
-int app_set_led (uint16_t id, bool led_state, int verbosity)
+void app_set_led (uint16_t id, bool led_state)
 {
    if (id == APP_DATA_LED_ID)
    {
@@ -51,29 +55,19 @@ int app_set_led (uint16_t id, bool led_state, int verbosity)
    {
       gpio_set (GPIO_LED2, led_state ? 1 : 0); /* "LED2" on circuit board */
    }
-
-   return 0;
 }
 
-void app_get_button (const app_data_t * p_appdata, uint16_t id, bool * p_pressed)
+bool app_get_button (uint16_t id)
 {
    if (id == 0)
    {
-      *p_pressed = (gpio_get (GPIO_BUTTON1) == 0);
+      return (gpio_get (GPIO_BUTTON1) == 0);
    }
    else if (id == 1)
    {
-      *p_pressed = (gpio_get (GPIO_BUTTON2) == 0);
-   }
-   else
-   {
-      *p_pressed = false;
+      return (gpio_get (GPIO_BUTTON2) == 0);
    }
-}
-
-static void main_timer_tick (os_timer_t * timer, void * arg)
-{
-   os_event_set (gp_appdata->main_events, APP_EVENT_TIMER);
+   return false;
 }
 
 /************************* Shell commands *************************************/
@@ -105,14 +99,7 @@ static int _cmd_pnio_show (int argc, char * argv[])
       return -1;
    }
 
-   pnet_show (g_net, level);
-   printf ("\n");
-   printf (
-      "App parameter 1 = 0x%08x\n",
-      (unsigned)ntohl (gp_appdata->app_param_1));
-   printf (
-      "App parameter 2 = 0x%08x\n",
-      (unsigned)ntohl (gp_appdata->app_param_2));
+   pnet_show (app_get_pnet_instance (sample_app), level);
    printf ("\n");
    printf ("p-net revision: " PNET_VERSION "\n");
    printf ("rt-kernel revision: %s\n", os_kernel_revision);
@@ -130,7 +117,7 @@ SHELL_CMD (cmd_pnio_show);
 static int _cmd_pnio_factory_reset (int argc, char * argv[])
 {
    printf ("Factory reset\n");
-   (void)pnet_factory_reset (g_net);
+   (void)pnet_factory_reset (app_get_pnet_instance (sample_app));
 
    return 0;
 }
@@ -157,73 +144,86 @@ const shell_cmd_t cmd_pnio_remove_files = {
    .help_long = "Remove data files"};
 SHELL_CMD (cmd_pnio_remove_files);
 
+/**
+ * Suppress RT-Kernel banner during startup
+ */
+void shell_banner (void)
+{
+   /* Do not print message since it is run in another context
+    * and is not aligned with application log
+    */
+}
+
 /****************************** Main ******************************************/
 
 int main (void)
 {
-   int ret = -1;
-   app_data_t appdata;
-   g_net = NULL;
-   gp_appdata = &appdata;
-   uint16_t number_of_ports = 0;
-
-   /* Prepare appdata */
-   memset (&appdata, 0, sizeof (appdata));
-   appdata.alarm_allowed = true;
-   strcpy (appdata.arguments.eth_interfaces, APP_DEFAULT_ETHERNET_INTERFACE);
-   strcpy (appdata.arguments.station_name, APP_DEFAULT_STATION_NAME);
-   appdata.arguments.verbosity = (LOG_LEVEL <= LOG_LEVEL_WARNING) ? 1 : 0;
-
-   appdata.main_events = os_event_create();
-   appdata.main_timer =
-      os_timer_create (APP_TICK_INTERVAL_US, main_timer_tick, NULL, false);
-
-   printf ("\n** Profinet sample application **\n");
-   if (appdata.arguments.verbosity > 0)
-   {
-      printf (
-         "Number of slots:      %u (incl slot for DAP module)\n",
-         PNET_MAX_SLOTS);
-      printf ("P-net log level:      %u (DEBUG=0, FATAL=4)\n", LOG_LEVEL);
-      printf ("App verbosity level:  %u\n", appdata.arguments.verbosity);
-      printf ("Max number of ports:  %u\n", PNET_MAX_PHYSICAL_PORTS);
-      printf ("Network interfaces:   %s\n", appdata.arguments.eth_interfaces);
-      printf ("Default station name: %s\n", appdata.arguments.station_name);
-   }
+   int ret;
+   int32_t app_log_level = APP_LOG_LEVEL;
+   app_utils_netif_namelist_t netif_name_list;
+   pnet_if_cfg_t netif_cfg = {0};
+   uint16_t number_of_ports;
+
+   strcpy (app_args.eth_interfaces, APP_DEFAULT_ETHERNET_INTERFACE);
+   strcpy (app_args.station_name, APP_GSDML_DEFAULT_STATION_NAME);
+   app_log_set_log_level (app_log_level);
+
+   APP_LOG_INFO ("\n** Starting P-Net sample application " PNET_VERSION
+                 " **\n");
+   APP_LOG_INFO ("\nType help to a list ofsupported shell commands.\n"
+                 "Type help <cmd> to get a command description.\n"
+                 "For example: help pnio_show\n\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 ("Default station name: %s\n", app_args.station_name);
+
+   app_pnet_cfg_init_default (&pnet_cfg);
+
+   /* Note: station name is defined by app_gsdml.h */
+
+   strcpy (pnet_cfg.file_directory, APP_DEFAULT_FILE_DIRECTORY);
 
-   app_pnet_cfg_init_default (&pnet_default_cfg);
-   pnet_default_cfg.cb_arg = (void *)&appdata;
-   strcpy (pnet_default_cfg.station_name, appdata.arguments.station_name);
-   strcpy (pnet_default_cfg.file_directory, APP_DEFAULT_FILE_DIRECTORY);
    /* Note: pnal_cfg not is used for rt-kernel  */
 
-   ret = app_pnet_cfg_init_netifs (
-      appdata.arguments.eth_interfaces,
-      &appdata.if_list,
+   ret = app_utils_pnet_cfg_init_netifs (
+      app_args.eth_interfaces,
+      &netif_name_list,
       &number_of_ports,
-      &pnet_default_cfg,
-      appdata.arguments.verbosity);
+      &netif_cfg);
    if (ret != 0)
    {
       return -1;
    }
-   pnet_default_cfg.num_physical_ports = number_of_ports;
+
+   pnet_cfg.if_cfg = netif_cfg;
+   pnet_cfg.num_physical_ports = number_of_ports;
+
+   app_utils_print_network_config (&netif_cfg, number_of_ports);
 
    /* Initialize profinet stack */
-   g_net = pnet_init (&pnet_default_cfg);
-   if (g_net == NULL)
+   APP_LOG_INFO ("Init sample application\n");
+   sample_app = app_init (&pnet_cfg);
+   if (sample_app == NULL)
    {
-      printf ("Failed to initialize p-net application.\n");
+      printf ("Failed to initialize P-Net.\n");
+      printf ("Aborting application\n");
       return -1;
    }
 
-   os_timer_start (gp_appdata->main_timer);
-
-   app_loop_forever (g_net, &appdata);
+   APP_LOG_INFO ("Start sample application\n");
+   if (app_start (sample_app, RUN_IN_MAIN_THREAD) != 0)
+   {
+      printf ("Failed to start\n");
+      printf ("Aborting application\n");
+      return -1;
+   }
 
-   os_timer_destroy (appdata.main_timer);
-   os_event_destroy (appdata.main_events);
-   printf ("Quitting the sample application\n\n");
+   app_loop_forever (sample_app);
 
    return 0;
 }

+ 1 - 1
test/utils_for_testing.cpp

@@ -559,7 +559,7 @@ void PnetIntegrationTestBase::run_stack (int us)
       if ((appdata.main_arep != 0) && (appdata.tick_ctr > 10))
       {
          appdata.tick_ctr = 0;
-         appdata.inputdata[0] = appdata.data_ctr++;
+         appdata.inputdata[0] = appdata.counter_data++;
 
          /* Set data for custom input modules, if any */
          for (slot = 0; slot < PNET_MAX_SLOTS; slot++)

+ 1 - 1
test/utils_for_testing.h

@@ -110,7 +110,7 @@ typedef struct app_data_for_testing_obj
    os_timer_t * periodic_timer;
    pnet_event_values_t cmdev_state;
    uint16_t data_cycle_ctr;
-   uint32_t data_ctr;
+   uint32_t counter_data;
    os_event_t * main_events;
    uint32_t main_arep;
    bool alarm_allowed;

Деякі файли не було показано, через те що забагато файлів було змінено