)
set (TARGET ${PROJECT_NAME})
-set (SOURCES server.c opc_util.c opc_app.h wdf_utils.c wdf_utils.h)
+set (SOURCES server.c opc_util.c opc_app.h wdf_utils.c wdf_utils.h wdf_origins.h wdf_props.cpp wdf_props.h)
add_subdirectory(wdf-linux/wdflib wdflib)
option (APP_USE_OPEN62541_SUBMODULE "Use the open62541 submodule" ON)
if (APP_USE_OPEN62541_SUBMODULE)
+ if (MSVC)
+ set (UA_MSVC_FORCE_STATIC_CRT OFF CACHE BOOL "Allow linking to msvcrt")
+ endif()
set (UA_ENABLE_DISCOVERY ON CACHE BOOL "Enable UA Discovery")
- set (UA_ENABLE_PUBSUB ON CACHE BOOL "Enable PubSub protocol support")
+ #set (UA_ENABLE_DISCOVERY_MULTICAST ON CACHE BOOL "Enable UA Discovery multicast")
+ #set (UA_ENABLE_PUBSUB ON CACHE BOOL "Enable PubSub protocol support")
add_subdirectory(open62541) # target open62541::open62541
else()
set (CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) # prefer open62541's own config file
endif()
add_executable(${TARGET} ${SOURCES})
-target_compile_features(${TARGET} PUBLIC c_std_11)
+target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17)
target_include_directories(${TARGET} PUBLIC open62541::open62541 ${wdflib_SOURCE_DIR})
target_link_libraries(${TARGET} PUBLIC open62541::open62541 wdflib)
#include <open62541/server.h>
#include <open62541/client.h>
#include <wdf.h>
+#include "wdf_origins.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum nodeset_t {
+ NodeSet_Monitor,
+ NodeSet_Custom
+};
typedef struct {
UA_Server *server;
UA_Client *regClient;
- UA_Int64 regId;
UA_Int16 ns;
+ enum nodeset_t nodeset;
const char *regUri;
int filehandle;
WdfHeader hdr;
+ uint32_t exposureTime; // in milliseconds
+ UA_Variant instrumentType;
uint64_t spectrumIndex;
+ int64_t spectrumTime;
float *xlist;
float *ilist;
+ wdf_origin_t *origins;
+ int originCount;
} App;
UA_StatusCode add_scalar(App *app, UA_NodeId parentNode,
UA_NodeId *newNodeId);
UA_StatusCode add_vector(App *app, UA_NodeId parentNode,
const char *fullname, const char *displayName,
- size_t len, UA_Float *value, const UA_DataType *valueType);
+ size_t len, void *value, const UA_DataType *valueType);
UA_StatusCode update_scalar(App *app, UA_NodeId nodeId, void *valuePtr, const UA_DataType *typePtr);
UA_StatusCode update_float_vector(App *app, UA_NodeId nodeId, size_t len, const float *vector);
-UA_StatusCode update_xlist(App *app);
-UA_StatusCode update_ilist(App *app);
+UA_StatusCode update_xlist(App *app, const char *nodename);
+UA_StatusCode update_ilist(App *app, const char *nodename);
+#ifdef __cplusplus
+}
+#endif
#endif // _opc_app_h_INCLUDE
return addVariableNode(app->server, app->ns, parentNode, fullname, displayName, attr);
}
-UA_StatusCode add_vector(App *app, UA_NodeId parentNode, const char *fullname, const char *displayName, size_t len, UA_Float *value, const UA_DataType *valueType)
+UA_StatusCode add_vector(App *app, UA_NodeId parentNode, const char *fullname, const char *displayName, size_t len, void *value, const UA_DataType *valueType)
{
UA_Int32 dims = (UA_Int32)len;
UA_VariableAttributes attr = UA_VariableAttributes_default;
return UA_Server_writeValue(app->server, nodeId, value);
}
-UA_StatusCode update_xlist(App *app)
+UA_StatusCode update_xlist(App *app, const char *nodename)
{
UA_StatusCode status = UA_STATUSCODE_BADNOTFOUND;
WdfBlock section = {0};
uint64_t pos = wdf_find_section(app->filehandle, WDF_BLOCKID_XLIST, WDF_BLOCKID_ANY, §ion);
if (pos != (uint64_t)-1)
{
- ssize_t count = wdf_read_xlist(app->filehandle, app->hdr.npoints, app->xlist);
+ int64_t count = wdf_read_xlist(app->filehandle, app->hdr.npoints, app->xlist);
if (count > 0)
{
- UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, "renishaw.spd.spectrum.xlist");
+ UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, nodename);
status = update_float_vector(app, node, app->hdr.npoints, app->xlist);
}
}
return status;
}
-UA_StatusCode update_ilist(App *app)
+UA_StatusCode update_ilist(App *app, const char *nodename)
{
WdfBlock section = {0};
uint64_t pos = wdf_find_section(app->filehandle, WDF_BLOCKID_DATA, WDF_BLOCKID_ANY, §ion);
wdf_read_spectrum(app->filehandle, pos, app->hdr.npoints, app->spectrumIndex, app->ilist);
- UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, "renishaw.spd.spectrum.ilist");
+ UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, nodename);
return update_float_vector(app, node, app->hdr.npoints, app->ilist);
}
-
#include <wdf.h>
#include "opc_app.h"
#include "wdf_utils.h"
+#include "wdf_props.h"
#define DISCOVERY_SERVER_ENDPOINT "opc.tcp://localhost:4840"
}
#endif
-static int usage(int exitCode)
-{
- fprintf(stderr, "usage: opc_server ?-port NUM? ?-register? ?-interval MS? FILENAME\n");
- exit(exitCode);
-}
-
// Register the server with LDS
// periodic server register after 10 Minutes, delay first register for 500ms
UA_StatusCode registerServer(App *app)
if (UA_StatusCode_isGood(status))
{
status = UA_Server_addPeriodicServerRegisterCallback(app->server,
- app->regClient, app->regUri, 10 * 60 * 1000, 500, &app->regId);
+ app->regClient, app->regUri, 10 * 60 * 1000, 500, NULL);
}
return status;
}
static void getSpectrumCallback(UA_Server *server, void *clientData)
{
+ enum {INDEX, TIME, FLAGS, XLIST, ILIST };
+ struct map_t { const char *names[2]; };
+ struct map_t ID[6] = {
+ { "Index", "renishaw.spd.spectrum.index" },
+ { "Time", "renishaw.spd.spectrum.time" },
+ { "Flags", "renishaw.spd.spectrum.flags" },
+ { "XList", "renishaw.spd.spectrum.xlist" },
+ { "YList", "renishaw.spd.spectrum.ilist" }
+ };
+
App *app = (App *)clientData;
if (app->spectrumIndex == 0)
- update_xlist(app);
- update_ilist(app);
+ update_xlist(app, ID[XLIST].names[app->nodeset]);
+ update_ilist(app, ID[ILIST].names[app->nodeset]);
- UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, "renishaw.spd.spectrum.index");
+ UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, ID[INDEX].names[app->nodeset]);
update_scalar(app, node, &app->spectrumIndex, &UA_TYPES[UA_TYPES_INT64]);
+ for (int n = 0; n < app->originCount; ++n)
+ {
+ if (app->origins[n].type == WdfDataType_Time)
+ {
+ UA_NodeId nodeTime = UA_NODEID_STRING_ALLOC(app->ns, ID[TIME].names[app->nodeset]);
+ origin_value_t value = {0};
+ wdf_get_origin_values(app->filehandle, &app->hdr, &app->origins[n], app->spectrumIndex, app->spectrumIndex, &value);
+ update_scalar(app, nodeTime, &value.time, &UA_TYPES[UA_TYPES_DATETIME]);
+ }
+ else if (app->origins[n].type == WdfDataType_Flags)
+ {
+ UA_NodeId node = UA_NODEID_STRING_ALLOC(app->ns, ID[FLAGS].names[app->nodeset]);
+ origin_value_t value = { 0 };
+ wdf_get_origin_values(app->filehandle, &app->hdr, &app->origins[n], app->spectrumIndex, app->spectrumIndex, &value);
+ update_scalar(app, node, &value.flags, &UA_TYPES[UA_TYPES_UINT64]);
+ }
+ }
+
++app->spectrumIndex;
if (app->spectrumIndex > app->hdr.ncollected)
app->spectrumIndex = 0;
}
-static UA_StatusCode add_nodes(App *app)
+static UA_StatusCode add_nodes_custom(App *app)
{
// Add new object node for our data as Renishaw
UA_NodeId mainNode, spdNode;
status = add_scalar(app, spdNode, "renishaw.spd.npoints", "Points", &app->hdr.npoints, &UA_TYPES[UA_TYPES_INT32]);
if (UA_StatusCode_isGood(status))
status = add_scalar(app, spdNode, "renishaw.spd.laserwavenum", "Laser Wavenumber", &app->hdr.laserwavenum, &UA_TYPES[UA_TYPES_FLOAT]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, spdNode, "renishaw.spd.exposuretime", "Exposure Time", &app->exposureTime, &UA_TYPES[UA_TYPES_UINT32]);
if (UA_StatusCode_isGood(status))
{
UA_NodeId spectrumNode;
status = add_object_node(app, spdNode, "renishaw.spd.spectrum", "Spectrum", &spectrumNode);
if (UA_StatusCode_isGood(status))
status = add_scalar(app, spectrumNode, "renishaw.spd.spectrum.index", "Index", &app->spectrumIndex, &UA_TYPES[UA_TYPES_INT64]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, spectrumNode, "renishaw.spd.spectrum.time", "Time", &app->spectrumTime, &UA_TYPES[UA_TYPES_DATETIME]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, spectrumNode, "renishaw.spd.spectrum.flags", "Flags", &app->spectrumIndex, &UA_TYPES[UA_TYPES_UINT64]);
if (UA_StatusCode_isGood(status))
status = add_vector(app, spectrumNode, "renishaw.spd.spectrum.xlist", "XList", app->hdr.npoints, app->xlist, &UA_TYPES[UA_TYPES_FLOAT]);
if (UA_StatusCode_isGood(status))
return status;
}
+// Create a node tree that matches the Renishaw Monitor application
+static UA_StatusCode add_nodes_monitor(App *app)
+{
+ // Add new object node for our data as Renishaw
+ UA_NodeId mainNode;
+ UA_StatusCode status = add_object_node(app, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), "Renishaw", "Renishaw", &mainNode);
+ if (UA_StatusCode_isGood(status))
+ {
+ UA_NodeId subNode;
+ float datavalues[2] = { 0, 0 };
+ UA_String name1 = UA_STRING("Intensity At Point 700");
+ UA_String name2 = UA_STRING("Signal To Baseline From 500 To 600");
+ UA_String names[2] = { name1, name2 };
+ status = add_object_node(app, mainNode, "Analysis", "Analysis", &subNode);
+ if (UA_StatusCode_isGood(status))
+ status = add_vector(app, subNode, "Data Value", "Data Value", 2, datavalues, &UA_TYPES[UA_TYPES_FLOAT]);
+ if (UA_StatusCode_isGood(status))
+ status = add_vector(app, subNode, "ResultName", "ResultName", 2, names, &UA_TYPES[UA_TYPES_STRING]);
+ }
+ if (UA_StatusCode_isGood(status))
+ {
+ UA_NodeId subNode;
+ status = add_object_node(app, mainNode, "Info", "Info", &subNode);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Laser Wavenumber", "Laser Wavenumber", &app->hdr.laserwavenum, &UA_TYPES[UA_TYPES_FLOAT]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Exposure Time", "Exposure Time", &app->exposureTime, &UA_TYPES[UA_TYPES_UINT32]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Instrument Type", "Instrument Type", app->instrumentType.data, &UA_TYPES[UA_TYPES_STRING]);
+ }
+ if (UA_StatusCode_isGood(status))
+ {
+ UA_NodeId subNode;
+ status = add_object_node(app, mainNode, "Spectrum", "Spectrum", &subNode);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Index", "Index", &app->spectrumIndex, &UA_TYPES[UA_TYPES_INT64]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Time", "Time", &app->spectrumTime, &UA_TYPES[UA_TYPES_DATETIME]);
+ if (UA_StatusCode_isGood(status))
+ status = add_scalar(app, subNode, "Flags", "Flags", &app->spectrumIndex, &UA_TYPES[UA_TYPES_UINT64]);
+ if (UA_StatusCode_isGood(status))
+ status = add_vector(app, subNode, "XList", "XList", app->hdr.npoints, app->xlist, &UA_TYPES[UA_TYPES_FLOAT]);
+ if (UA_StatusCode_isGood(status))
+ status = add_vector(app, subNode, "IList", "IList", app->hdr.npoints, app->ilist, &UA_TYPES[UA_TYPES_FLOAT]);
+ }
+ return status;
+}
+
+static int usage(int exitCode)
+{
+ fprintf(stderr, "usage: opc_server ?-port NUM? ?-register? ?-interval MS? ?-custom? FILENAME\n");
+ exit(exitCode);
+}
+
int main(int argc, char** argv)
{
App application = {0};
{
app->regUri = DISCOVERY_SERVER_ENDPOINT;
}
+ else if (strncmp("-custom", argv[n], 7) == 0)
+ {
+ app->nodeset = NodeSet_Custom;
+ }
else if (strncmp("-interval", argv[n], 9) == 0)
{
interval = strtoul(argv[n+1], NULL, 0);
fprintf(stderr, "failed to read wdf file\n");
exit(1);
}
+ app->originCount = wdf_get_origins(app->filehandle, &app->hdr, &app->origins);
+ if (app->origins == NULL || app->originCount == 0)
+ {
+ fprintf(stderr, "failed to get data origins\n");
+ exit(1);
+ }
size_t len = sizeof(float) * app->hdr.npoints;
app->xlist = (float *)UA_malloc(len);
memset(app->ilist, 0, len);
app->spectrumIndex = 0;
+ WdfBlock propsBlock;
+ uint64_t pos = wdf_find_section(app->filehandle, WDF_BLOCKID_MEASUREMENT, WDF_BLOCKID_ANY, &propsBlock);
+ app->exposureTime = wdf_get_scan_exposuretime(filename, pos);
+ pos = wdf_find_section(app->filehandle, WDF_BLOCKID_INSTRUMENT, WDF_BLOCKID_ANY, &propsBlock);
+ UA_Variant_init(&app->instrumentType);
+ wdf_get_property(filename, pos, "Instrument type", &app->instrumentType);
+
app->server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(app->server);
UA_StatusCode status = UA_ServerConfig_setMinimal(config, port, NULL);
#endif
if (UA_StatusCode_isGood(status))
- status = add_nodes(app);
+ {
+ if (app->nodeset == NodeSet_Custom)
+ status = add_nodes_custom(app);
+ else
+ status = add_nodes_monitor(app);
+ }
if (UA_StatusCode_isGood(status))
{
-Subproject commit 4581e0fb267ccb22d5983c69d00ff1e5be008897
+Subproject commit fb10eff367ad0a3d4b64f6d363080dc27d44b0b6
--- /dev/null
+// Copyright (c) 2023 Renishaw plc. All rights reserved.
+//
+
+#ifndef _wdf_origins_h_INCLUDE
+#define _wdf_origins_h_INCLUDE
+
+#include <wdf.h>
+
+#pragma pack(push,1)
+typedef struct {
+ union {
+ struct {
+ uint32_t type : 31;
+ uint32_t alternate : 1;
+ };
+ uint32_t datatype;
+ };
+ uint32_t units;
+ char label[16];
+ uint64_t pos; // offset of the start of the origin data
+} wdf_origin_t;
+#pragma pack(pop)
+
+typedef union origin_value_type {
+ double value;
+ uint64_t flags;
+ uint64_t time;
+} origin_value_t;
+
+#endif // _wdf_origins_h_INCLUDE;
--- /dev/null
+#include "wdf_props.h"
+#include <fstream>
+#include <wdf.h>
+#include <pset.h>
+#include <open62541/types_generated.h>
+#include "wdf_utils.h"
+
+constexpr int WDF_BLOCK_HDR_SIZE = 16;
+
+// works for WXDA properties - get by name.
+UA_StatusCode wdf_get_property(const char *filename, uint64_t pos, const char *keyname, UA_Variant *valuePtr)
+{
+ UA_StatusCode status = UA_STATUSCODE_BAD;
+ std::ifstream file(filename, std::ios::in | std::ios::binary);
+ file.seekg(pos + WDF_BLOCK_HDR_SIZE, std::ios::beg);
+ uint32_t len = Pset::IsPset(file);
+ if (len)
+ {
+ const auto itemlist = Pset::ParseStream(file, len);
+ Pset pset(itemlist, nullptr);
+ if (pset.exists(keyname))
+ {
+ status = UA_STATUSCODE_GOOD;
+ const auto item = pset.get_item(keyname);
+ switch (item->hdr.type)
+ {
+ case PSET_TYPE_INT:
+ UA_Variant_setScalar(valuePtr, const_cast<int32_t*>(&item->val.lVal), &UA_TYPES[UA_TYPES_INT32]);
+ break;
+ case PSET_TYPE_FLOAT:
+ UA_Variant_setScalar(valuePtr, const_cast<float*>(&item->val.fltVal), &UA_TYPES[UA_TYPES_FLOAT]);
+ break;
+ case PSET_TYPE_DOUBLE:
+ UA_Variant_setScalar(valuePtr, const_cast<double*>(&item->val.dblVal), &UA_TYPES[UA_TYPES_DOUBLE]);
+ break;
+ case PSET_TYPE_STRING:
+ {
+ UA_String str = { 0 };
+ str.length = item->length;
+ str.data = (UA_Byte*)UA_malloc(item->length);
+ memcpy(str.data, item->val.strVal, item->length);
+ UA_Variant_setScalarCopy(valuePtr, &str, &UA_TYPES[UA_TYPES_STRING]);
+ break;
+ }
+ default:
+ status = UA_STATUSCODE_BAD;
+ }
+ }
+ }
+ return status;
+}
+
+uint32_t wdf_get_scan_exposuretime(const char* filename, uint64_t pos)
+{
+ uint32_t value = 0;
+ std::ifstream file(filename, std::ios::in | std::ios::binary);
+ file.seekg(pos + WDF_BLOCK_HDR_SIZE, std::ios::beg);
+ uint32_t len = Pset::IsPset(file);
+ if (len)
+ {
+ const auto itemlist = Pset::ParseStream(file, len);
+ Pset pset(itemlist, nullptr);
+ const auto& niitem= pset.get_item("NamedItems");
+ Pset nameditems(niitem->val.childVal, &pset);
+ const auto& scanitem = nameditems.get_item("Scan");
+ Pset scan(scanitem->val.childVal, &nameditems);
+ const auto extime = scan.get_item("Exposure Time");
+ value = extime->val.ulVal;
+ }
+ return value;
+}
--- /dev/null
+#ifndef _wdf_props_h_INCLUDE
+#define _wdf_props_h_INCLUDE
+
+#include <stdint.h>
+#include <open62541/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+uint32_t wdf_get_property(const char *filename, uint64_t pos, const char *keyname, UA_Variant *value);
+uint32_t wdf_get_scan_exposuretime(const char* filename, uint64_t pos);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // _wdf_props_h_INCLUDE
\ No newline at end of file
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
+#include "wdf_utils.h"
#include "opc_app.h"
int wdf_open(const char *filename, WdfHeader *wdfPtr)
/// @param[in] length - number of elements to be read
/// @param[out] xlist - pointer to array to be filled
/// @return -1 on error or the number of bytes read into the spectrum
-ssize_t wdf_read_xlist(int fd, uint32_t length, float *xlist)
+int64_t wdf_read_xlist(int fd, uint32_t length, float *xlist)
{
ssize_t r = -1;
WdfBlock section = {0};
if (r > 0)
r = _read(fd, xlist, sizeof(float) * length);
}
- return r;
+ return (int64_t)r;
}
int wdf_read_spectrum(int fd, uint64_t base, uint32_t length, uint64_t spectrum, float *ilist)
r = _read(fd, ilist, sizeof(float)*length);
return r;
}
+
+// Get the data origin description headers and returns the number of origins or 0
+// caller needs to free the origins array.
+int wdf_get_origins(int fd, WdfHeader *hdr, wdf_origin_t **originsPtr)
+{
+ WdfBlock section = {0};
+ uint64_t pos = wdf_find_section(fd, WDF_BLOCKID_ORIGIN, WDF_BLOCKID_ANY, §ion);
+ uint32_t originCount = 0;
+ uint64_t originSize = hdr->nspectra * sizeof(uint64_t);
+ int r = _read(fd, &originCount, sizeof(originCount));
+ wdf_origin_t *origins = (wdf_origin_t *)malloc(sizeof(wdf_origin_t) * originCount);
+ if (origins == NULL)
+ {
+ perror("malloc");
+ originCount = 0;
+ }
+ else
+ {
+ for (uint32_t n = 0; n < originCount; ++n)
+ {
+ _read(fd, &origins[n].datatype, sizeof(uint32_t));
+ _read(fd, &origins[n].units, sizeof(uint32_t));
+ _read(fd, &origins[n].label, 16);
+ origins[n].pos = _lseeki64(fd, 0, SEEK_CUR);
+ _lseeki64(fd, originSize, SEEK_CUR);
+ }
+ *originsPtr = origins;
+ }
+ return originCount;
+}
+
+int wdf_get_origin_values(int fd, WdfHeader *hdr, wdf_origin_t *origin, uint64_t start, uint64_t end, origin_value_t *dataPtr)
+{
+ uint64_t offset = sizeof(double) * start;
+ uint32_t byteCount = (uint32_t)(((end - start) + 1) * sizeof(double));
+ int64_t pos = _lseeki64(fd, origin->pos + offset, SEEK_SET);
+ int r = _read(fd, dataPtr, byteCount);
+ if (r != byteCount)
+ fprintf(stderr, "error: misread in wdf_get_orign_values\n");
+ return r;
+}
#ifndef _wdf_utils_h_INCLUDE
#define _wdf_utils_h_INCLUDE
+#include <sys/types.h>
+#include <stddef.h>
#include <wdf.h>
+#include "wdf_origins.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
int wdf_open(const char *filename, WdfHeader *wdfPtr);
int wdf_close(int fd);
uint64_t wdf_find_section(int fd, uint32_t id, uint32_t uid, WdfBlock *blockPtr);
-ssize_t wdf_read_xlist(int fd, uint32_t length, float *xlist);
+int64_t wdf_read_xlist(int fd, uint32_t length, float *xlist);
int wdf_read_spectrum(int fd, uint64_t base, uint32_t length, uint64_t spectrum, float *ilist);
+int wdf_get_origins(int fd, WdfHeader *hdr, wdf_origin_t **originsPtr);
+int wdf_get_origin_values(int fd, WdfHeader *hdr, wdf_origin_t *origin, uint64_t start, uint64_t end, origin_value_t *dataPtr);
+#ifdef __cplusplus
+}
+#endif
#endif // _wdf_utils_h_INCLUDE