Explorar o código

Merge pull request #269 from EIPStackGroup/Issue_257_Add_NV

Closes #257 adds NV mechanism for QoS object
Martin Melik-Merkumians %!s(int64=6) %!d(string=hai) anos
pai
achega
f58a0aa30d

+ 8 - 7
source/buildsupport/OpENer.cmake

@@ -6,11 +6,11 @@ ENDFUNCTION(opener_add_definition)
 
 ##############################################
 # Adds platform specific include directories #
-############################################## 
+##############################################
 macro(opener_platform_support ARGS)
 
   if(OpENer_PLATFORM STREQUAL "")
-    message(FATAL_ERROR "No platform selected!")  
+    message(FATAL_ERROR "No platform selected!")
   endif(OpENer_PLATFORM STREQUAL "")
 
   include( ${OpENer_BUILDSUPPORT_DIR}/${OpENer_PLATFORM}/OpENer_PLATFORM_INCLUDES.cmake)
@@ -20,15 +20,16 @@ endmacro(opener_platform_support ARGS)
 
 #######################################
 # Adds common Include directories     #
-####################################### 
+#######################################
 macro(opener_common_includes)
   set( SRC_DIR "${PROJECT_SOURCE_DIR}/src" )
   set( CIP_SRC_DIR "${SRC_DIR}/cip" )
   set( ENET_ENCAP_SRC_DIR "${SRC_DIR}/enet_encap" )
   set( PORTS_SRC_DIR "${SRC_DIR}/ports")
+  set( NVDATA_SRC_DIR "${SRC_DIR}/ports/nvdata")
   set( UTILS_SRC_DIR "${SRC_DIR}/utils")
 
-  include_directories( ${PROJECT_SOURCE_DIR} ${SRC_DIR} ${CIP_SRC_DIR} ${CIP_CONNETION_MANAGER_SRC_DIR} ${ENET_ENCAP_SRC_DIR} ${PORTS_SRC_DIR} ${UTILS_SRC_DIR} ${OpENer_CIP_OBJECTS_DIR} )
+  include_directories( ${PROJECT_SOURCE_DIR} ${SRC_DIR} ${CIP_SRC_DIR} ${CIP_CONNETION_MANAGER_SRC_DIR} ${ENET_ENCAP_SRC_DIR} ${PORTS_SRC_DIR} ${UTILS_SRC_DIR} ${OpENer_CIP_OBJECTS_DIR} ${NVDATA_SRC_DIR} )
   include_directories( "${PROJECT_BINARY_DIR}/src/ports" )
 endmacro(opener_common_includes)
 
@@ -46,7 +47,7 @@ ENDMACRO(opener_add_cip_object)
 
 #######################################
 # Creates options for trace level     #
-####################################### 
+#######################################
 macro(createTraceLevelOptions)
   add_definitions( -DOPENER_WITH_TRACES )
   set( TRACE_LEVEL 0 )
@@ -54,7 +55,7 @@ macro(createTraceLevelOptions)
   set( OpENer_TRACE_LEVEL_WARNING ON CACHE BOOL "Warning trace level" )
   set( OpENer_TRACE_LEVEL_STATE ON CACHE BOOL "State trace level" )
   set( OpENer_TRACE_LEVEL_INFO ON CACHE BOOL "Info trace level" )
-  
+
   if(OpENer_TRACE_LEVEL_ERROR)
     math( EXPR TRACE_LEVEL "${TRACE_LEVEL} + 1" )
   endif(OpENer_TRACE_LEVEL_ERROR)
@@ -67,6 +68,6 @@ macro(createTraceLevelOptions)
   if(OpENer_TRACE_LEVEL_INFO)
     math( EXPR TRACE_LEVEL "${TRACE_LEVEL} + 8" )
   endif(OpENer_TRACE_LEVEL_INFO)
-  
+
   add_definitions(-DOPENER_TRACE_LEVEL=${TRACE_LEVEL})
 endmacro(createTraceLevelOptions)

+ 54 - 1
source/src/cip/cipassembly.c

@@ -24,6 +24,20 @@ EipStatus SetAssemblyAttributeSingle(CipInstance *const instance,
                                      const struct sockaddr *originator_address,
                                      const int encapsulation_session);
 
+static EipStatus AssemblyPreGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+);
+
+static EipStatus AssemblyPostSetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+);
+
 /** @brief Constructor for the assembly object class
  *
  *  Creates an initializes Assembly class or object instances
@@ -51,6 +65,8 @@ CipClass *CreateAssemblyClass(void) {
                   kSetAttributeSingle,
                   &SetAssemblyAttributeSingle,
                   "SetAssemblyAttributeSingle");
+    InsertGetSetCallback(assembly_class, AssemblyPreGetCallback, kPreGetFunc);
+    InsertGetSetCallback(assembly_class, AssemblyPostSetCallback, kPostSetFunc);
   }
 
   return assembly_class;
@@ -105,7 +121,7 @@ CipInstance *CreateAssemblyObject(const EipUint32 instance_id,
                   3,
                   kCipByteArray,
                   assembly_byte_array,
-                  kSetAndGetAble);
+                  kSetAndGetAble | kPreGetFunc | kPostSetFunc);
   /* Attribute 4 Number of bytes in Attribute 3 */
   InsertAttribute(instance,
                   4,
@@ -178,6 +194,13 @@ EipStatus SetAssemblyAttributeSingle(CipInstance *const instance,
             message_router_response->general_status = kCipErrorTooMuchData;
           }
           else{
+            if (attribute->attribute_flags & kPreSetFunc
+                && instance->cip_class->PreSetCallback) {
+                instance->cip_class->PreSetCallback(instance,
+                                                    attribute,
+                                                    message_router_request->service);
+            }
+
             memcpy(data->data, router_request_data, data->length);
 
             if(AfterAssemblyDataReceived(instance) != kEipStatusOk) {
@@ -212,3 +235,33 @@ EipStatus SetAssemblyAttributeSingle(CipInstance *const instance,
 
   return kEipStatusOkSend;
 }
+
+static EipStatus AssemblyPreGetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  int rc;
+  (void) attribute; (void) service; /* no unused parameter warnings */
+
+  rc = BeforeAssemblyDataSend(instance);
+
+  return rc;
+}
+
+static EipStatus AssemblyPostSetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  int rc;
+  (void) attribute; (void) service; /* no unused parameter warnings */
+
+  rc = AfterAssemblyDataReceived(instance);
+
+  return rc;
+}

+ 31 - 11
source/src/cip/cipcommon.c

@@ -249,7 +249,7 @@ CipClass *CreateCipClass(const CipUdint class_code,
   class->class_instance.instance_number = 0;       /* the class object is instance zero of the class it describes (weird, but that's the spec)*/
   class->class_instance.attributes = 0;       /* this will later point to the class attibutes*/
   class->class_instance.cip_class = meta_class;       /* the class's class is the metaclass (like SmallTalk)*/
-  class->class_instance.next = 0;       /* the next link will always be zero, sinc there is only one instance of any particular class object */
+  class->class_instance.next = 0;       /* the next link will always be zero, since there is only one instance of any particular class object */
 
   meta_class->class_instance.instance_number = 0xffffffff;       /*the metaclass object does not really have a valid instance number*/
   meta_class->class_instance.attributes = 0;       /* the metaclass has no attributes*/
@@ -383,6 +383,28 @@ void InsertService(const CipClass *const class,
   /* adding more services than were declared is a no-no*/
 }
 
+void InsertGetSetCallback
+(
+  CipClass *const cip_class,
+  CipGetSetCallback callback_function,
+  CIPAttributeFlag  callbacks_to_install
+) {
+  if (0 != (kPreGetFunc & callbacks_to_install)) {
+    cip_class->PreGetCallback = callback_function;
+  }
+  if (0 != (kPostGetFunc & callbacks_to_install)) {
+    cip_class->PostGetCallback = callback_function;
+  }
+  if (0 != (kPreSetFunc & callbacks_to_install)) {
+    cip_class->PreSetCallback = callback_function;
+  }
+  /* The PostSetCallback is used for both, the after set action and the storage
+   * of non volatile data. Therefore check for both flags set. */
+  if (0 != ((kPostSetFunc | kNvDataFunc) & callbacks_to_install)) {
+    cip_class->PostSetCallback = callback_function;
+  }
+}
+
 CipAttributeStruct *GetCipAttribute(const CipInstance *const instance,
                                     const EipUint16 attribute_number) {
 
@@ -437,16 +459,9 @@ EipStatus GetAttributeSingle(CipInstance *RESTRICT const instance,
       OPENER_TRACE_INFO("getAttribute %d\n",
                         message_router_request->request_path.attribute_number);                 /* create a reply message containing the data*/
 
-      /*TODO think if it is better to put this code in an own
-       * getAssemblyAttributeSingle functions which will call get attribute
-       * single.
-       */
-
-      if (attribute->type == kCipByteArray
-          && instance->cip_class->class_code == kCipAssemblyClassCode) {
-        /* we are getting a byte array of a assembly object, kick out to the app callback */
-        OPENER_TRACE_INFO(" -> getAttributeSingle CIP_BYTE_ARRAY\r\n");
-        BeforeAssemblyDataSend(instance);
+      /* Call the PreGetCallback if enabled for this attribute and the class provides one. */
+      if (attribute->attribute_flags & kPreGetFunc && NULL != instance->cip_class->PreGetCallback) {
+        instance->cip_class->PreGetCallback(instance, attribute, message_router_request->service);
       }
 
       OPENER_ASSERT(NULL != attribute)
@@ -454,6 +469,11 @@ EipStatus GetAttributeSingle(CipInstance *RESTRICT const instance,
                                                         attribute->data,
                                                         &message);
       message_router_response->general_status = kCipErrorSuccess;
+
+      /* Call the PostGetCallback if enabled for this attribute and the class provides one. */
+      if (attribute->attribute_flags & kPostGetFunc && NULL != instance->cip_class->PostGetCallback) {
+        instance->cip_class->PostGetCallback(instance, attribute, message_router_request->service);
+      }
     }
   }
 

+ 19 - 5
source/src/cip/cipqos.c

@@ -92,9 +92,23 @@ EipStatus SetAttributeSingleQoS(
       OPENER_TRACE_INFO(" setAttribute %d\n", attribute_number);
 
       if(NULL != attribute->data) {
+        /* Call the PreSetCallback if enabled. */
+        if (attribute->attribute_flags & kPreSetFunc
+            && NULL != instance->cip_class->PreSetCallback) {
+          instance->cip_class->PreSetCallback(instance, attribute,
+                                            message_router_request->service);
+        }
+
         CipUsint *data = (CipUsint *) attribute->data;
         *(data) = attribute_value_received;
 
+        /* Call the PostSetCallback if enabled. */
+        if (attribute->attribute_flags & (kPostSetFunc | kNvDataFunc)
+            && NULL != instance->cip_class->PostSetCallback) {
+          instance->cip_class->PostSetCallback(instance, attribute,
+                                            message_router_request->service);
+        }
+
         message_router_response->general_status = kCipErrorSuccess;
       } else {
         message_router_response->general_status = kCipErrorNotEnoughData;
@@ -184,27 +198,27 @@ EipStatus CipQoSInit() {
                   4,
                   kCipUsint,
                   (void *) &g_qos.dscp.urgent,
-                  kGetableSingle | kSetable);
+                  kGetableSingle | kSetable | kNvDataFunc);
   InsertAttribute(instance,
                   5,
                   kCipUsint,
                   (void *) &g_qos.dscp.scheduled,
-                  kGetableSingle | kSetable);
+                  kGetableSingle | kSetable | kNvDataFunc);
   InsertAttribute(instance,
                   6,
                   kCipUsint,
                   (void *) &g_qos.dscp.high,
-                  kGetableSingle | kSetable);
+                  kGetableSingle | kSetable | kNvDataFunc);
   InsertAttribute(instance,
                   7,
                   kCipUsint,
                   (void *) &g_qos.dscp.low,
-                  kGetableSingle | kSetable);
+                  kGetableSingle | kSetable | kNvDataFunc);
   InsertAttribute(instance,
                   8,
                   kCipUsint,
                   (void *) &g_qos.dscp.explicit,
-                  kGetableSingle | kSetable);
+                  kGetableSingle | kSetable | kNvDataFunc);
 
   InsertService(qos_class, kGetAttributeSingle, &GetAttributeSingleQoS,
                 "GetAttributeSingleQoS");

+ 61 - 20
source/src/cip/ciptypes.h

@@ -107,7 +107,13 @@ typedef enum { /* TODO: Rework */
   kSetable = 0x04, /**< Set-able via Set Attribute */
   /* combined for convenience */
   kSetAndGetAble = 0x07, /**< both set and get-able */
-  kGetableSingleAndAll = 0x03 /**< both single and all */
+  kGetableSingleAndAll = 0x03, /**< both single and all */
+  /* Flags to control the usage of callbacks per attribute from the Get* and Set* services */
+  kPreGetFunc = 0x10, /**< enable pre get callback */
+  kPostGetFunc = 0x20,  /**< enable post get callback */
+  kPreSetFunc = 0x40, /**< enable pre set callback */
+  kPostSetFunc = 0x80,  /**< enable post set callback */
+  kNvDataFunc = 0x80, /**< enable Non Volatile data callback, is the same as @ref kPostSetFunc */
 } CIPAttributeFlag;
 
 typedef enum {
@@ -216,18 +222,21 @@ typedef struct {
                      request */
 } CipMessageRouterResponse;
 
+/** @brief Structure to describe a single CIP attribute of an object
+*/
 typedef struct {
-  EipUint16 attribute_number;
-  EipUint8 type;
-  CIPAttributeFlag attribute_flags; /*< 0 => getable_all, 1 => getable_single; 2 =>
-                                       setable_single; 3 => get and setable; all other
-                                       values reserved */
+  EipUint16 attribute_number; /**< The attribute number of this attribute. */
+  EipUint8 type;  /**< The @ref CipDataType of this attribute. */
+  CIPAttributeFlag attribute_flags; /**< See @ref CIPAttributeFlag declaration for valid values. */
   void *data;
 } CipAttributeStruct;
 
-/* type definition of CIP service structure */
 
-/* instances are stored in a linked list*/
+/** @brief Type definition of one instance of an Ethernet/IP object
+ *
+ *  All instances are stored in a linked list that originates from the CipClass::instances
+ *  pointer of the @ref CipClass structure.
+ */
 typedef struct cip_instance {
   EipUint32 instance_number; /**< this instance's number (unique within the class) */
   CipAttributeStruct *attributes; /**< pointer to an array of attributes which
@@ -237,26 +246,58 @@ typedef struct cip_instance {
                                 in a linked list */
 } CipInstance;
 
-/** @brief Class is a subclass of Instance */
+/** @ingroup CIP_API
+ *  @typedef EipStatus (*CipGetSetCallback)(
+ *    CipInstance *const instance,
+ *    CipAttributeStruct *const attribute,
+ *    CipByte service
+ *  )
+ *  @brief Signature definition of callback functions for Set and Get services
+ *
+ *  @param  instance  CIP instance involved in the Set or Get service
+ *  @param  attribute CIP attribute involved in the Set or Get service
+ *  @param  service   service code of currently executed service
+ *  @return           status of kEipStatusOk or kEipStatusError on failure
+ */
+typedef EipStatus (*CipGetSetCallback)
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+);
+
+/** @brief Type definition of CipClass that is a subclass of CipInstance */
 typedef struct cip_class {
-  CipInstance class_instance;
+  CipInstance class_instance; /**< This is the instance that contains the
+                                  class attributes of this class. */
   /* the rest of these are specific to the Class class only. */
   CipUdint class_code; /**< class code */
   EipUint16 revision; /**< class revision*/
   EipUint16 number_of_instances; /**< number of instances in the class (not
-                                    including instance 0)*/
-  EipUint16 number_of_attributes; /**< number of attributes of each instance*/
+                                    including instance 0) */
+  EipUint16 number_of_attributes; /**< number of attributes of each instance */
   EipUint16 highest_attribute_number; /**< highest defined attribute number
                                          (attribute numbers are not necessarily
-                                         consecutive)*/
-  uint8_t *get_single_bit_mask; /**< Bitmask for GetAttributeSingle*/
-  uint8_t *set_bit_mask; /**< Bitmask for SetAttributeSingle*/
-  uint8_t *get_all_bit_mask; /**< Bitmask for GetAttributeAll*/
-
-  EipUint16 number_of_services; /**< number of services supported*/
-  CipInstance *instances; /**< pointer to the list of instances*/
-  struct cip_service_struct *services; /**< pointer to the array of services*/
+                                         consecutive) */
+  uint8_t *get_single_bit_mask; /**< bit mask for GetAttributeSingle */
+  uint8_t *set_bit_mask; /**< bit mask for SetAttributeSingle */
+  uint8_t *get_all_bit_mask; /**< bit mask for GetAttributeAll */
+
+  EipUint16 number_of_services; /**< number of services supported */
+  CipInstance *instances; /**< pointer to the list of instances */
+  struct cip_service_struct *services; /**< pointer to the array of services */
   char *class_name; /**< class name */
+  /** Is called in GetAttributeSingle* before the response is assembled from
+  * the object's attributes */
+  CipGetSetCallback PreGetCallback;
+  /** Is called in GetAttributeSingle* after the response has been sent. */
+  CipGetSetCallback PostGetCallback;
+  /** Is called in SetAttributeSingle* before the received data is moved
+  * to the object's attributes */
+  CipGetSetCallback PreSetCallback;
+  /** Is called in SetAttributeSingle* after the received data was set
+  * in the object's attributes. */
+  CipGetSetCallback PostSetCallback;
 } CipClass;
 
 /** @ingroup CIP_API

+ 22 - 0
source/src/opener_api.h

@@ -243,6 +243,28 @@ void InsertService(const CipClass *const cip_class_to_add_service,
                    const CipServiceFunction service_function,
                    char *const service_name);
 
+/** @ingroup CIP_API
+ * @brief Insert a Get or Set callback for a CIP class
+ *
+ * @param cip_class pointer to the target CIP object
+ * @param callback_function the callback function to insert
+ * @param callbacks_to_install  flags to select the affected callbacks
+ *
+ * This function inserts the provided @p callback_function into selected
+ *  callback function entries of the CIP class @p cip_class.
+ * The callback targets are selected by @p callbacks_to_install that may
+ *  be an ORed mask of kPreGetFunc, kPostGetFunc, kPreSetFunc, kPostSetFunc
+ *  and kNvDataFunc.
+ * If either the kPostSetFunc or kNvDataFunc is set the same function
+ *  pointer CipClass::PostSetCallback will be called.
+ */
+void InsertGetSetCallback
+(
+  CipClass *const cip_class,
+  CipGetSetCallback callback_function,
+  CIPAttributeFlag  callbacks_to_install
+);
+
 /** @ingroup CIP_API
  * @brief Produce the data according to CIP encoding onto the message buffer.
  *

+ 3 - 2
source/src/ports/CMakeLists.txt

@@ -3,6 +3,7 @@
 #######################################
 
 add_subdirectory( ${OpENer_PLATFORM} )
+add_subdirectory( nvdata )
 
 #######################################
 # Add common includes                 #
@@ -14,9 +15,9 @@ opener_common_includes()
 #######################################
 opener_platform_support("INCLUDES")
 
-set( PLATFORM_GENERIC_SRC generic_networkhandler.c socket_timer.c  udp_protocol.c)
+set( PLATFORM_GENERIC_SRC generic_networkhandler.c socket_timer.c  udp_protocol.c )
 
-add_library( PLATFORM_GENERIC ${PLATFORM_GENERIC_SRC})
+add_library( PLATFORM_GENERIC ${PLATFORM_GENERIC_SRC} )
 
 if( OPENER_BUILD_SHARED_LIBS )
   install(TARGETS PLATFORM_GENERIC

+ 3 - 3
source/src/ports/MINGW/CMakeLists.txt

@@ -14,11 +14,11 @@ opener_platform_support("INCLUDES")
 
 set (PLATFORMLIBNAME ${OpENer_PLATFORM}PLATFORM)
 
-add_library( ${PLATFORMLIBNAME} ${PLATFORM_SPEC_SRC}) 
+add_library( ${PLATFORMLIBNAME} ${PLATFORM_SPEC_SRC})
 
 add_executable(OpENer main.c)
 
-target_link_libraries( OpENer PLATFORM_GENERIC ${PLATFORMLIBNAME} CIP Utils  SAMPLE_APP ENET_ENCAP ws2_32 IPHLPAPI ${OpENer_CIP_OBJECTS} )
+target_link_libraries( OpENer PLATFORM_GENERIC ${PLATFORMLIBNAME} CIP Utils SAMPLE_APP ENET_ENCAP NVDATA ws2_32 IPHLPAPI ${OpENer_CIP_OBJECTS} )
 
 # Add additional CIP Objects
 string(COMPARE NOTEQUAL "${OpENer_ADD_CIP_OBJECTS}" "" OpENer_HAS_ADDITIONAL_OBJECT )
@@ -30,4 +30,4 @@ if( OpENer_HAS_ADDITIONAL_OBJECT )
   endforeach()
 else()
   message(STATUS "No additional activated objects")
-endif()
+endif()

+ 9 - 0
source/src/ports/MINGW/main.c

@@ -12,6 +12,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
+#include "nvdata.h"
 
 extern int newfd;
 
@@ -62,6 +63,14 @@ int main(int argc,
   /* Setup the CIP Layer */
   CipStackInit(nUniqueConnectionID);
 
+  /* The CIP objects are now created and initialized with their default values.
+   *  Now any NV data values are loaded to change the data to the stored
+   *  configuration.
+   */
+  if (kEipStatusError == NvdataLoad()) {
+    OPENER_TRACE_WARN("Loading of some NV data failed. Maybe the first start?\n");
+  }
+
   /* Setup Network Handles */
   if ( kEipStatusOk == NetworkHandlerInitialize() ) {
     g_end_stack = 0;

+ 41 - 0
source/src/ports/MINGW/sample_application/sampleapplication.c

@@ -9,8 +9,10 @@
 
 #include "opener_api.h"
 #include "appcontype.h"
+#include "trace.h"
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
+#include "nvqos.h"
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -19,6 +21,39 @@
 #define DEMO_APP_HEARTBEAT_LISTEN_ONLY_ASSEMBLY_NUM 153 //0x099
 #define DEMO_APP_EXPLICT_ASSEMBLY_NUM              154 //0x09A
 
+/* local functions */
+/** Implementation of the PostSetCallback for QoS class
+*
+* @param  instance  pointer to instance of QoS class
+* @param  attribute pointer to attribute structure
+* @param  service   the CIP service code of current request
+*
+* This function implements the PostSetCallback for the QoS class. The
+* purpose of this function is to save the NV attributes of the QoS
+* class instance to external storage.
+*
+* This application specific implementation chose to save all attributes
+* at once using a single NvQosStore() call.
+*/
+static EipStatus QosSetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  EipStatus status = kEipStatusOk;
+
+  if (0 != (kNvDataFunc & attribute->attribute_flags)) {
+    OPENER_TRACE_INFO("NV data update: %s, i %" PRIu32 ", a %" PRIu16 "\n",
+                      instance->cip_class->class_name,
+                      instance->instance_number,
+                      attribute->attribute_number);
+    status = NvQosStore(&g_qos);
+  }
+  return status;
+}
+
 /* global variables for demo application (4 assembly data fields)  ************/
 
 EipUint8 g_assembly_data064[40]; /* Input */
@@ -62,6 +97,12 @@ EipStatus ApplicationInitialization(void) {
                                      DEMO_APP_INPUT_ASSEMBLY_NUM,
                                      DEMO_APP_CONFIG_ASSEMBLY_NUM);
 
+  /* For NV data support connect callback functions for each object class with
+   *  NV data.
+   */
+  InsertGetSetCallback(GetCipClass(kCipQoSClassCode), QosSetCallback,
+                       kNvDataFunc);
+
   return kEipStatusOk;
 }
 

+ 3 - 3
source/src/ports/POSIX/CMakeLists.txt

@@ -3,7 +3,7 @@ add_subdirectory(sample_application)
 set( PLATFORM_SPEC_SRC networkhandler.c opener_error.c networkconfig.c)
 
 #######################################
-# OpENer RT patch	              #
+# OpENer RT patch	                    #
 #######################################
 set( OpENer_RT OFF CACHE BOOL "Activate OpENer RT" )
 if(OpENer_RT)
@@ -41,7 +41,7 @@ if( OPENER_BUILD_SHARED_LIBS )
 endif()
 
 add_executable(OpENer main.c)
-target_link_libraries( OpENer CIP Utils SAMPLE_APP ENET_ENCAP PLATFORM_GENERIC ${OpENer_PLATFORM}PLATFORM ${PLATFORM_SPEC_LIBS} rt cap pthread)
+target_link_libraries( OpENer CIP Utils SAMPLE_APP ENET_ENCAP NVDATA PLATFORM_GENERIC ${OpENer_PLATFORM}PLATFORM ${PLATFORM_SPEC_LIBS} rt cap pthread)
 
 # Add additional CIP Objects
 string(COMPARE NOTEQUAL "${OpENer_ADD_CIP_OBJECTS}" "" OpENer_HAS_ADDITIONAL_OBJECT )
@@ -53,4 +53,4 @@ if( OpENer_HAS_ADDITIONAL_OBJECT )
   endforeach()
 else()
   message(STATUS "No additional activated objects")
-endif()
+endif()

+ 9 - 0
source/src/ports/POSIX/main.c

@@ -22,6 +22,7 @@
 #include "networkconfig.h"
 #include "doublylinkedlist.h"
 #include "cipconnectionobject.h"
+#include "nvdata.h"
 
 /******************************************************************************/
 /** @brief Signal handler function for ending stack execution
@@ -111,6 +112,14 @@ int main(int argc,
   /* Setup the CIP Layer */
   CipStackInit(unique_connection_id);
 
+  /* The CIP objects are now created and initialized with their default values.
+   *  Now any NV data values are loaded to change the data to the stored
+   *  configuration.
+   */
+  if (kEipStatusError == NvdataLoad()) {
+    OPENER_TRACE_WARN("Loading of some NV data failed. Maybe the first start?\n");
+  }
+
   /* Setup Network Handles */
   if (kEipStatusOk == NetworkHandlerInitialize() ) {
     g_end_stack = 0;

+ 1 - 0
source/src/ports/POSIX/sample_application/CMakeLists.txt

@@ -10,3 +10,4 @@ opener_common_includes()
 opener_platform_support("INCLUDES")
 
 add_library(SAMPLE_APP sampleapplication.c)
+target_link_libraries( SAMPLE_APP NVDATA )

+ 41 - 0
source/src/ports/POSIX/sample_application/sampleapplication.c

@@ -14,6 +14,7 @@
 #include "cipidentity.h"
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
+#include "nvqos.h"
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -29,6 +30,40 @@ EipUint8 g_assembly_data096[32]; /* Output */
 EipUint8 g_assembly_data097[10]; /* Config */
 EipUint8 g_assembly_data09A[32]; /* Explicit */
 
+/* local functions */
+/** Implementation of the PostSetCallback for QoS class
+*
+* @param  instance  pointer to instance of QoS class
+* @param  attribute pointer to attribute structure
+* @param  service   the CIP service code of current request
+*
+* This function implements the PostSetCallback for the QoS class. The
+* purpose of this function is to save the NV attributes of the QoS
+* class instance to external storage.
+*
+* This application specific implementation chose to save all attributes
+* at once using a single NvQosStore() call.
+*/
+static EipStatus QosSetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  EipStatus status = kEipStatusOk;
+
+  if (0 != (kNvDataFunc & attribute->attribute_flags)) {
+    OPENER_TRACE_INFO("NV data update: %s, i %" PRIu32 ", a %" PRIu16 "\n",
+                      instance->cip_class->class_name,
+                      instance->instance_number,
+                      attribute->attribute_number);
+    status = NvQosStore(&g_qos);
+  }
+  return status;
+}
+
+/* global functions called by the stack */
 EipStatus ApplicationInitialization(void) {
   /* create 3 assembly object instances*/
   /*INPUT*/
@@ -65,6 +100,12 @@ EipStatus ApplicationInitialization(void) {
                                      DEMO_APP_INPUT_ASSEMBLY_NUM,
                                      DEMO_APP_CONFIG_ASSEMBLY_NUM);
 
+  /* For NV data support connect callback functions for each object class with
+   *  NV data.
+   */
+  InsertGetSetCallback(GetCipClass(kCipQoSClassCode), QosSetCallback,
+                       kNvDataFunc);
+
   return kEipStatusOk;
 }
 

+ 3 - 3
source/src/ports/WIN32/CMakeLists.txt

@@ -14,11 +14,11 @@ opener_platform_support("INCLUDES")
 
 set (PLATFORMLIBNAME ${OpENer_PLATFORM}PLATFORM)
 
-add_library( ${PLATFORMLIBNAME} ${PLATFORM_SPEC_SRC}) 
+add_library( ${PLATFORMLIBNAME} ${PLATFORM_SPEC_SRC})
 
 add_executable(OpENer main.c)
 
-target_link_libraries( OpENer PLATFORM_GENERIC ${PLATFORMLIBNAME} CIP Utils  SAMPLE_APP ENET_ENCAP ws2_32 ${OpENer_CIP_OBJECTS} )
+target_link_libraries( OpENer PLATFORM_GENERIC ${PLATFORMLIBNAME} CIP Utils  SAMPLE_APP ENET_ENCAP NVDATA ws2_32 ${OpENer_CIP_OBJECTS} )
 
 # Add additional CIP Objects
 string(COMPARE NOTEQUAL "${OpENer_ADD_CIP_OBJECTS}" "" OpENer_HAS_ADDITIONAL_OBJECT )
@@ -30,4 +30,4 @@ if( OpENer_HAS_ADDITIONAL_OBJECT )
   endforeach()
 else()
   message(STATUS "No additional activated objects")
-endif()
+endif()

+ 9 - 0
source/src/ports/WIN32/main.c

@@ -12,6 +12,7 @@
 #include "cipcommon.h"
 #include "trace.h"
 #include "networkconfig.h"
+#include "nvdata.h"
 
 extern int newfd;
 
@@ -62,6 +63,14 @@ int main(int argc,
   /* Setup the CIP Layer */
   CipStackInit(nUniqueConnectionID);
 
+  /* The CIP objects are now created and initialized with their default values.
+   *  Now any NV data values are loaded to change the data to the stored
+   *  configuration.
+   */
+  if (kEipStatusError == NvdataLoad()) {
+    OPENER_TRACE_WARN("Loading of some NV data failed. Maybe the first start?\n");
+  }
+
   /* Setup Network Handles */
   if ( kEipStatusOk == NetworkHandlerInitialize() ) {
     g_end_stack = 0;

+ 41 - 0
source/src/ports/WIN32/sample_application/sampleapplication.c

@@ -7,9 +7,11 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "trace.h"
 #include "opener_api.h"
 #include "ciptcpipinterface.h"
 #include "cipqos.h"
+#include "nvqos.h"
 
 #define DEMO_APP_INPUT_ASSEMBLY_NUM                100 //0x064
 #define DEMO_APP_OUTPUT_ASSEMBLY_NUM               150 //0x096
@@ -18,6 +20,39 @@
 #define DEMO_APP_HEARTBEAT_LISTEN_ONLY_ASSEMBLY_NUM 153 //0x099
 #define DEMO_APP_EXPLICT_ASSEMBLY_NUM              154 //0x09A
 
+/* local functions */
+/** Implementation of the PostSetCallback for QoS class
+*
+* @param  instance  pointer to instance of QoS class
+* @param  attribute pointer to attribute structure
+* @param  service   the CIP service code of current request
+*
+* This function implements the PostSetCallback for the QoS class. The
+* purpose of this function is to save the NV attributes of the QoS
+* class instance to external storage.
+*
+* This application specific implementation chose to save all attributes
+* at once using a single NvQosStore() call.
+*/
+static EipStatus QosSetCallback
+(
+  CipInstance *const instance,
+  CipAttributeStruct *const attribute,
+  CipByte service
+)
+{
+  EipStatus status = kEipStatusOk;
+
+  if (0 != (kNvDataFunc & attribute->attribute_flags)) {
+    OPENER_TRACE_INFO("NV data update: %s, i %" PRIu32 ", a %" PRIu16 "\n",
+                      instance->cip_class->class_name,
+                      instance->instance_number,
+                      attribute->attribute_number);
+    status = NvQosStore(&g_qos);
+  }
+  return status;
+}
+
 /* global variables for demo application (4 assembly data fields)  ************/
 
 EipUint8 g_assembly_data064[32]; /* Input */
@@ -61,6 +96,12 @@ EipStatus ApplicationInitialization(void) {
                                      DEMO_APP_INPUT_ASSEMBLY_NUM,
                                      DEMO_APP_CONFIG_ASSEMBLY_NUM);
 
+  /* For NV data support connect callback functions for each object class with
+   *  NV data.
+   */
+  InsertGetSetCallback(GetCipClass(kCipQoSClassCode), QosSetCallback,
+                       kNvDataFunc);
+
   return kEipStatusOk;
 }
 

+ 1 - 1
source/src/ports/generic_networkhandler.h

@@ -4,7 +4,7 @@
  *
  ******************************************************************************/
 
-/** @file generic_networkhandler.c
+/** @file generic_networkhandler.h
  *  @author Martin Melik Merkumians
  *  @brief This file includes all platform-independent functions of the network handler to reduce code duplication
  *

+ 30 - 0
source/src/ports/nvdata/CMakeLists.txt

@@ -0,0 +1,30 @@
+#######################################
+# Non Volatile data storage library   #
+#######################################
+
+set( NVDATA_SRC nvdata.c conffile.c nvqos.c )
+
+#######################################
+# Add common includes                 #
+#######################################
+opener_common_includes()
+
+#######################################
+# Add platform-specific includes      #
+#######################################
+opener_platform_support("INCLUDES")
+
+add_library( NVDATA ${NVDATA_SRC} )
+
+if( OPENER_BUILD_SHARED_LIBS )
+  install(TARGETS NVDATA
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
+  )
+  install(DIRECTORY ${NVDATA_SRC_DIR}
+    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
+    FILES_MATCHING PATTERN "*.h"
+   )
+endif()
+

+ 133 - 0
source/src/ports/nvdata/conffile.c

@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file nvqos.c
+ *  @brief This file implements the functions to handle QoS object's NV data.
+ *
+ *  This is only proof-of-concept code. Don't use it in a real product.
+ *  Please think about atomic update of the external file and doing something
+ *  like fsync() to flush the data really to disk.
+ */
+/* COMPILATION SWITCHES */
+#define ENABLE_VERBOSE  0   /* Enable this to observe internal operation */
+
+/*   INCLUDES          */
+#include "conffile.h"
+
+#include <errno.h>
+#include <string.h>
+
+#if defined _WIN32
+  #include <direct.h>
+#else
+  #include <sys/stat.h>
+  #include <sys/types.h>
+#endif /* if defined _WIN32 */
+
+#include "trace.h"
+#include "opener_error.h"
+
+/** Base path for configuration file store */
+#define CFG_BASE  "nvdata/"
+
+#if ENABLE_VERBOSE != 0
+#define VERBOSE(pFile, pFmt, ...)   do { fprintf(pFile, pFmt, ##__VA_ARGS__); } while (0)
+#else
+#define VERBOSE(pFile, pFmt, ...)
+#endif
+
+
+/** @brief Portable wrapper for mkdir(). Internally used by RecMkdir()
+  *
+  * @param[in] path the full path of the directory to create
+  * @return zero on success, otherwise -1 and errno set
+  */
+static inline int Mkdir(const char *path)
+{
+#ifdef _WIN32
+  return _mkdir(path);
+#else
+  /* The mode of 0777 will be constrained by umask. */
+  return mkdir(path, 0777);
+#endif
+}
+
+
+static void RecMkdir(char * const p_path)
+{
+  char *sep = strrchr(p_path, '/' );
+  if(sep && p_path != sep) {  /* "p_path != sep" avoids mkdir("/")! */
+    *sep = '\0';
+    RecMkdir(p_path);
+    *sep = '/';
+  }
+  VERBOSE(stdout, " ->mkdir('%s')", p_path);
+  if(Mkdir(p_path) && EEXIST != errno) {
+    char *error_message = GetErrorMessage(errno);
+    OPENER_TRACE_ERR("error while trying to create '%s', %d - %s\n",
+                     p_path, errno, error_message);
+    FreeErrorMessage(error_message);
+  }
+}
+
+
+static FILE *FopenMkdir(char *p_path, char *mode)
+{
+  char *sep = strrchr(p_path, '/' );
+  /* In write mode create missing directories. */
+  if(sep && 'w' == *mode) {
+    *sep = '\0';
+    RecMkdir(p_path);
+    *sep = '/';
+    VERBOSE(stdout, "%s", "\n");
+  }
+  return fopen(p_path, mode);
+}
+
+
+/** @brief Open the named configuration file possibly for write operation
+ *
+ *  @param  write   Open for write?
+ *  @param  p_name  pointer to file name string
+ *  @param  p_filep pointer to FILE* to initialize
+ *  @return       0: success; -1: failure, errno set
+ *
+ *  This function open a configuration file, possibly for write operation,
+ *  in the NV data storage directory.
+ */
+int ConfFileOpen(bool write, const char *p_name, FILE **p_filep)
+{
+  char  path_buf[64];
+  int   rc;
+
+  rc = snprintf(path_buf, sizeof path_buf, "%s%s", CFG_BASE, p_name);
+  if (rc > 0) {
+    FILE *filep = FopenMkdir(path_buf, write ? "w" : "r");
+    if (filep) {
+      rc = 0;
+      *p_filep  = filep;
+    }
+    else {
+      rc = EOF;
+    }
+  }
+  return rc;
+}
+
+/** @brief Close the configuration file associated with the FILE* given
+ *
+ *  @param  p_filep pointer to FILE* to close
+ *  @return       0: success; -1: failure, errno set
+ *
+ *  Closes the configuration file associated to p_filep. No data
+ *  synchronization to disk yet.
+ */
+int ConfFileClose(FILE **p_filep)
+{
+  int rc = fclose(*p_filep);
+  *p_filep = NULL;
+  return rc;
+}

+ 21 - 0
source/src/ports/nvdata/conffile.h

@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file conffile.h
+ *  @brief The interface to handle a configuration file in an abstracted way.
+ *
+ */
+#ifndef _CONFFILE_H_
+#define _CONFFILE_H_
+
+#include <stdbool.h>
+#include <stdio.h>
+
+int ConfFileOpen(bool write, const char *p_name, FILE **p_filep);
+
+int ConfFileClose(FILE **p_filep);
+
+#endif  /* _CONFFILE_H_ */

+ 42 - 0
source/src/ports/nvdata/nvdata.c

@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file nvdata.c
+ *  @brief This file implements the functions to load all needed Non Volatile data.
+ *
+ */
+#include "nvdata.h"
+
+/* Include headers of objects that need support for NV data here. */
+#include "nvqos.h"
+
+/** @brief Load NV data for all object classes
+ *
+ *  @return kEipStatusOk on success, kEipStatusError if failure for any object occurred
+ *
+ * This function loads the NV data for each object class that supports NV data from
+ *  external storage. If any of the load routines fails then for that object class
+ *  the current object instance values are written as new NV data. That should be
+ *  the default data.
+ *
+ * The load routines should be of the form
+ *    int Nv<ObjClassName>Load(<ObjectInstanceDataType> *p_obj_instance);
+ *  and return (-1) on failure and (0) on success.
+ */
+EipStatus NvdataLoad(void) {
+  EipStatus status = kEipStatusOk;
+  int       rc;
+
+  /* Load NV data for QoS object instance */
+  rc = NvQosLoad(&g_qos);
+  status |= rc;
+  if (0 != rc) {
+    rc = NvQosStore(&g_qos);
+    status |= rc;
+  }
+
+  return status;
+}

+ 19 - 0
source/src/ports/nvdata/nvdata.h

@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file nvdata.h
+ *  @brief This file provides the interface to load all needed Non Volatile data.
+ *
+ */
+
+#ifndef NVDATA_H_
+#define NVDATA_H_
+
+#include "typedefs.h"
+
+EipStatus NvdataLoad(void);
+
+#endif  /* ifndef NVDATA_H_ */

+ 85 - 0
source/src/ports/nvdata/nvqos.c

@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file nvqos.c
+ *  @brief This file implements the functions to handle QoS object's NV data.
+ *
+ *  This is only proof-of-concept code. Don't use it in a real product.
+ *  Please think about atomic update of the external file or better parsing
+ *  of the data on input.
+ */
+#include "nvqos.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "conffile.h"
+
+#define QOS_CFG_NAME  "qos.cfg"
+
+
+/** @brief Load NV data of the QoS object from file
+ *
+ *  @param  p_qos pointer to the QoS object's data structure
+ *  @return       0: success; -1: failure
+ */
+int NvQosLoad(CipQosObject *p_qos)
+{
+  CipQosObject  qos;
+  FILE  *p_file;
+  int   rd_cnt = 0;
+  int   rc;
+
+  rc = ConfFileOpen(false, QOS_CFG_NAME, &p_file);
+  if (0 == rc) {
+    /* Read input data */
+    memset(&qos, 0x00, sizeof qos);
+    rd_cnt = fscanf(p_file, " %" SCNu8 ", %" SCNu8 ", %" SCNu8 ", %" SCNu8 ", %" SCNu8 "\n",
+                    &qos.dscp.urgent, &qos.dscp.scheduled, &qos.dscp.high,
+                    &qos.dscp.low, &qos.dscp.explicit);
+
+    /* Need to try to close all stuff in any case. */
+    rc = ConfFileClose(&p_file);
+  }
+  if (0 == rc) {
+    /* If all data were read copy them to the global QoS object. */
+    if (5 == rd_cnt) {
+      p_qos->dscp.urgent = qos.dscp.urgent;
+      p_qos->dscp.scheduled = qos.dscp.scheduled;
+      p_qos->dscp.high = qos.dscp.high;
+      p_qos->dscp.low = qos.dscp.low;
+      p_qos->dscp.explicit = qos.dscp.explicit;
+    }
+  }
+  return rc;
+}
+
+/** @brief Store NV data of the QoS object to file
+ *
+ *  @param  p_qos pointer to the QoS object's data structure
+ *  @return       0: success; -1: failure
+ */
+int NvQosStore(const CipQosObject *p_qos)
+{
+  FILE  *p_file;
+  int   rc;
+
+  rc = ConfFileOpen(true, QOS_CFG_NAME, &p_file);
+  if (rc >= 0) {
+    /* Print output data */
+    rc = fprintf(p_file, " %" PRIu8 ", %" PRIu8 ", %" PRIu8 ", %" PRIu8 ", %" PRIu8 "\n",
+                 p_qos->dscp.urgent, p_qos->dscp.scheduled, p_qos->dscp.high,
+                 p_qos->dscp.low, p_qos->dscp.explicit);
+    if (rc > 0) {
+      rc = 0;
+    }
+
+    /* Need to try to close all stuff in any case. */
+    rc |= ConfFileClose(&p_file);
+  }
+  return rc;
+}

+ 22 - 0
source/src/ports/nvdata/nvqos.h

@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2019, Rockwell Automation, Inc.
+ * All rights reserved.
+ *
+ ******************************************************************************/
+
+/** @file nvqos.h
+ *  @brief This file provides the interface to handle QoS object's NV data.
+ *
+ */
+#ifndef _NVQOS_H_
+#define _NVQOS_H_
+
+#include "typedefs.h"
+
+#include "cipqos.h"
+
+int NvQosLoad(CipQosObject *p_qos);
+
+int NvQosStore(const CipQosObject *p_qos);
+
+#endif  /* _NVQOS_H_ */

+ 1 - 1
source/src/typedefs.h

@@ -81,7 +81,7 @@ typedef unsigned long long MicroSeconds;
    0 ..  success and there is no reply to send
    1 ... success and there is a reply to send
 
-   For both of these cases EIP_STATUS is the return type.
+   For both of these cases EipStatus is the return type.
 
    Other return type are:
    -- return pointer to thing, 0 if error (return type is "pointer to thing")

+ 1 - 0
source/tests/CMakeLists.txt

@@ -40,6 +40,7 @@ target_link_libraries( OpENer_Tests UtilsTest Utils )
 target_link_libraries( OpENer_Tests EthernetEncapsulationTest ENET_ENCAP )
 target_link_libraries( OpENer_Tests CipTest CIP )
 target_link_libraries( OpENer_Tests PortsTest PLATFORM_GENERIC )
+target_link_libraries( OpENer_Tests NVDATA )
 
 ########################################
 # Adds test to CTest environment       #