/*
 * Copyright (C) 2022 EXOLIGENT (www.exoligent.com)
 * Author: Maxime Coroyer <maxime.coroyer@exoligent.com>
 */
#include <open62541/plugin/log_stdout.h>    // UA_Log_Stdout

#include "gw.h"

#ifdef OPC_ENABLE_REALTIME_CAP
#include "opc-rt.h"
#endif

/**
 * Data source callbacks
 */
static UA_StatusCode opc_fip_report_read(UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *node_id, void *node_ctx,
    UA_Boolean src_tstp, const UA_NumericRange *range,
    UA_DataValue *data_value)
{
    struct gateway *gw = (struct gateway *) node_ctx;
    FipStatisticsSummary stats;

    memset(&stats, 0, sizeof(stats));
    stats.TxFrameOkCount = gw->fip.report.tx_err.ok;
    stats.TxFrameCollisionErrCount = gw->fip.report.tx_err.collision;
    stats.TxFrameConsistencyErrCount = gw->fip.report.tx_err.consistency;
    stats.TxVarNotFreshErrCount = gw->fip.report.tx_err.not_fresh;
    stats.RxFrameOkCount = gw->fip.report.rx_err.ok;
    stats.RxPreambleMissingErrCount = gw->fip.report.rx_err.pre_mis;
    stats.RxFrameStartDelimiterMissingErrCount = gw->fip.report.rx_err.fsd_mis;
    stats.RxFrameStartDelimiterUnknownErrCount = gw->fip.report.rx_err.fsd_unk;
    stats.RxFrameEndDelimiterMissingErrCount = gw->fip.report.rx_err.fed_mis;
    stats.RxCrcBadErrCount = gw->fip.report.rx_err.crc_bad;
    stats.RxVarPduBadErrCount = gw->fip.report.rx_err.pdu_bad;
    stats.RxVarLengthBadErrCount = gw->fip.report.rx_err.len_bad;
    stats.RxVarNotFreshErrCount = gw->fip.report.rx_err.not_fresh;
    stats.RxVarNotPromptErrCount = gw->fip.report.rx_err.not_prompt;

    UA_Variant_setScalarCopy(&data_value->value, &stats,
        &FipStatisticsSummaryType);

    data_value->hasValue = true;
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode opc_fip_report_write(UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *node_id, void *node_ctx,
    const UA_NumericRange *range, const UA_DataValue *data_value)
{
    return UA_STATUSCODE_BADINTERNALERROR;
}

#ifndef OPC_ENABLE_REALTIME_CAP
static UA_StatusCode opc_fip_var_read(UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *node_id, void *node_ctx,
    UA_Boolean src_tstp, const UA_NumericRange *range,
    UA_DataValue *data_value)
{
    struct pwrfip_var *v = ((struct pwrfip_var *) node_ctx);
    UA_ByteString data = {v->bsz, (UA_Byte *) v->buffer};

    UA_Variant_setScalarCopy(&data_value->value, &data,
        &UA_TYPES[UA_TYPES_BYTESTRING]);

    data_value->hasValue = true;
    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode opc_fip_var_write(UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *node_id, void *node_ctx,
    const UA_NumericRange *range, const UA_DataValue *data_value)
{
    struct pwrfip_var *v = ((struct pwrfip_var *) node_ctx);
    struct gw_var_cfg *v_cfg = (struct gw_var_cfg *) v->user_ctx;
    UA_Variant value = data_value->value;
    UA_ByteString *bstr = NULL;
    size_t bsz = 0;

    if (v_cfg->pwrfip_cfg.type != PWRFIP_VAR_TYPE_PROD)
        return UA_STATUSCODE_BADNOTWRITABLE;

    if (!UA_Variant_isScalar(&value) ||
        value.type != &UA_TYPES[UA_TYPES_BYTESTRING])
        return UA_STATUSCODE_BADINTERNALERROR;

    bstr = (UA_ByteString*) value.data;
    bsz = (bstr->length < v->bsz) ? bstr->length : v->bsz;
    memcpy(v->buffer, bstr->data, bsz);
    return UA_STATUSCODE_GOOD;
}
#endif

/**
 * OPC - FIP Constructors: Node, Var, ...
 */
static UA_StatusCode opc_fip_type_node_constructor(UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *type_id, void *type_ctx,
    const UA_NodeId *node_id, void **node_ctx)
{
    UA_Variant value;
    struct pwrfip_node *n = ((struct pwrfip_node *) *node_ctx);
    struct pwrfip_node_cfg *n_cfg = &n->infos->cfg;
    struct gateway *gw = (struct gateway *) n_cfg->user_ctx;
    FipStatisticsSummary stats;

    /* => fip.name */
    UA_Variant_init(&value);
    UA_String name = UA_STRING(gw->fip.name_str);
    UA_Variant_setScalar(&value, &name, &UA_TYPES[UA_TYPES_STRING]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.name"),
        value, node_id, NULL);

    /* => fip.addr */
    UA_Variant_init(&value);
    UA_Byte addr = n_cfg->fip_phy_addr;
    UA_Variant_setScalar(&value, &addr, &UA_TYPES[UA_TYPES_BYTE]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.addr"),
        value, node_id, NULL);

    /* => fip.seg */
    UA_Variant_init(&value);
    UA_Byte seg = n_cfg->fip_seg_num;
    UA_Variant_setScalar(&value, &seg, &UA_TYPES[UA_TYPES_BYTE]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.seg"),
        value, node_id, NULL);

    /* => fip.frm_type */
    UA_Variant_init(&value);
    UA_Int32 frm_type = n_cfg->fip_frm_type;
    UA_Variant_setScalar(&value, &frm_type, &UA_TYPES[UA_TYPES_INT32]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.frm_type"),
        value, node_id, NULL);

    /* => fip.bitrate */
    UA_Variant_init(&value);
    UA_Int32 bitrate = n_cfg->fip_bitrate;
    UA_Variant_setScalar(&value, &bitrate, &UA_TYPES[UA_TYPES_INT32]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.bitrate"),
        value, node_id, NULL);

    /* => fip.tr_ustime */
    UA_Variant_init(&value);
    UA_UInt32 tr_ustime = n_cfg->turn_around_ustime;
    UA_Variant_setScalar(&value, &tr_ustime, &UA_TYPES[UA_TYPES_UINT32]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.tr_ustime"),
        value, node_id, NULL);

    /* => fip.ts_ustime */
    UA_Variant_init(&value);
    UA_UInt32 ts_ustime = n_cfg->silence_ustime;
    UA_Variant_setScalar(&value, &ts_ustime, &UA_TYPES[UA_TYPES_UINT32]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.ts_ustime"),
        value, node_id, NULL);

    /* => fip.stats */
    UA_Variant_init(&value);
    UA_NodeId v_node_id = UA_NODEID_NULL;
    memset(&stats, 0, sizeof(stats));
    UA_Variant_setScalar(&value, &stats, &FipStatisticsSummaryType);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "fip.stats"),
        value, node_id, &v_node_id);
    /* + add data source */
    UA_DataSource data_src;
    data_src.read = opc_fip_report_read;
    data_src.write = opc_fip_report_write;
    UA_Server_setVariableNode_dataSource(srv, v_node_id, data_src);
    UA_Server_setNodeContext(srv, v_node_id, (void *) gw);

    /* => add "all variables" folder */
    UA_ObjectAttributes n_vars_attr = UA_ObjectAttributes_default;
    n_vars_attr.description = UA_LOCALIZEDTEXT("en_US", "FIP variables folder");
    n_vars_attr.displayName = UA_LOCALIZEDTEXT("en_US", "Variables");
    UA_Server_addObjectNode(srv,
        UA_NODEID_STRING(1, "fip.var.all_folder"),
        UA_NODEID_STRING(1, "fip"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, "fip.var.all_folder"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
        n_vars_attr, NULL, NULL);

    /* => add "prod variables" folder */
    UA_ObjectAttributes n_vars_p_attr = UA_ObjectAttributes_default;
    n_vars_p_attr.description = UA_LOCALIZEDTEXT("en_US", "FIP produced variables");
    n_vars_p_attr.displayName = UA_LOCALIZEDTEXT("en_US", "Prod");
    UA_Server_addObjectNode(srv,
        UA_NODEID_STRING(1, "fip.var.prod_folder"),
        UA_NODEID_STRING(1, "fip.var.all_folder"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, "fip.var.prod_folder"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
        n_vars_p_attr, NULL, NULL);

    /* => add "cons variables" folder */
    UA_ObjectAttributes n_vars_c_attr = UA_ObjectAttributes_default;
    n_vars_c_attr.description = UA_LOCALIZEDTEXT("en_US", "FIP consumed variables");
    n_vars_c_attr.displayName = UA_LOCALIZEDTEXT("en_US", "Cons");
    UA_Server_addObjectNode(srv,
        UA_NODEID_STRING(1, "fip.var.cons_folder"),
        UA_NODEID_STRING(1, "fip.var.all_folder"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, "fip.var.cons_folder"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE),
        n_vars_c_attr, NULL, NULL);

    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode opc_fip_type_var_constructor(
    UA_Server *srv,
    const UA_NodeId *sess_id, void *sess_ctx,
    const UA_NodeId *type_id, void *type_ctx,
    const UA_NodeId *node_id, void **node_ctx)
{
    UA_Variant value;
    struct pwrfip_var *v = ((struct pwrfip_var *) *node_ctx);
    struct gw_var_cfg *gw_v_cfg = (struct gw_var_cfg *) v->user_ctx;
    struct pwrfip_var_cfg *v_cfg = &gw_v_cfg->pwrfip_cfg;
    uint16_t flags = (v_cfg->type == PWRFIP_VAR_TYPE_CONS) ?
        v_cfg->cons.flags : v_cfg->prod.flags;
    uint32_t evt_type = (v_cfg->type == PWRFIP_VAR_TYPE_CONS) ?
        v_cfg->cons.evt_type : v_cfg->prod.evt_type;
    uint8_t buffer[1024];

    /* => var.name */
    UA_Variant_init(&value);
    UA_String name = UA_STRING(gw_v_cfg->name);
    UA_Variant_setScalar(&value, &name, &UA_TYPES[UA_TYPES_STRING]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.name"),
        value, node_id, NULL);

    /* => var.type */
    UA_Variant_init(&value);
    UA_Int32 type = v_cfg->type;
    UA_Variant_setScalar(&value, &type, &UA_TYPES[UA_TYPES_INT32]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.type"),
        value, node_id, NULL);

    /* => var.id */
    UA_Variant_init(&value);
    UA_UInt16 id = v_cfg->id;
    UA_Variant_setScalar(&value, &id, &UA_TYPES[UA_TYPES_UINT16]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.id"),
        value, node_id, NULL);

    /* => var.payload_bsz */
    UA_Variant_init(&value);
    UA_UInt16 payload_bsz = (v_cfg->type == PWRFIP_VAR_TYPE_CONS) ?
        v_cfg->cons.payload_bsz : v_cfg->prod.payload_bsz;
    UA_Variant_setScalar(&value, &payload_bsz, &UA_TYPES[UA_TYPES_UINT16]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.payload_bsz"),
        value, node_id, NULL);

    /* => var.refreshment */
    UA_Variant_init(&value);
    UA_Boolean refreshment = (flags & PWRFIP_VAR_FLAGS_REFRESH) ? true : false;
    UA_Variant_setScalar(&value, &refreshment, &UA_TYPES[UA_TYPES_BOOLEAN]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.refreshment"),
        value, node_id, NULL);

    /* => var.refreshment_ustime */
    if (refreshment && v_cfg->type == PWRFIP_VAR_TYPE_PROD) {
        UA_Variant_init(&value);
        UA_UInt32 refresh_us = v_cfg->prod.refreshment_ustime;
        UA_Variant_setScalar(&value, &refresh_us, &UA_TYPES[UA_TYPES_UINT32]);
        opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.refreshment_ustime"),
            value, node_id, NULL);
    }

    /* => var.promptness */
    if (v_cfg->type == PWRFIP_VAR_TYPE_CONS) {
        UA_Variant_init(&value);
        UA_Boolean promptness = (flags & PWRFIP_VAR_FLAGS_PROMPT) ? true : false;
        UA_Variant_setScalar(&value, &promptness, &UA_TYPES[UA_TYPES_BOOLEAN]);
        opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.promptness"),
            value, node_id, NULL);

        /* => var.promptness_ustime */
        if (promptness) {
            UA_Variant_init(&value);
            UA_UInt32 prompt_us = v_cfg->cons.promptness_ustime;
            UA_Variant_setScalar(&value, &prompt_us, &UA_TYPES[UA_TYPES_UINT32]);
            opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.promptness_ustime"),
                value, node_id, NULL);
        }
    }

    /* => var.event */
    UA_Variant_init(&value);
    UA_Boolean event = (evt_type & PWRFIP_EVT_TYPE_PERMANENT_ID) ? true : false;
    UA_Variant_setScalar(&value, &event, &UA_TYPES[UA_TYPES_BOOLEAN]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.event"),
        value, node_id, NULL);

    /* => var.data */
    UA_Variant_init(&value);
    memset(buffer, 0, sizeof(buffer));
    UA_NodeId v_node_id = UA_NODEID_NULL;
    UA_ByteString data = {v->bsz, (UA_Byte *) buffer};
    UA_Variant_setScalar(&value, &data, &UA_TYPES[UA_TYPES_BYTESTRING]);
    opc_value_trywrite(srv, UA_QUALIFIEDNAME(1, "var.data"),
        value, node_id, &v_node_id);
    /* + store v_node_id for later (pub/sub datasets) */
    gw_v_cfg->v_node_id = v_node_id;

#if defined OPC_ENABLE_REALTIME_CAP
    /* + realtime data value */
    UA_ByteString_init(&gw_v_cfg->rt_data);
    gw_v_cfg->rt_data.length = v->bsz;
    gw_v_cfg->rt_data.data = v->buffer;

    gw_v_cfg->rt_value = UA_DataValue_new();
    UA_Variant_setScalar(&gw_v_cfg->rt_value->value, &gw_v_cfg->rt_data,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    gw_v_cfg->rt_value->hasValue = UA_TRUE;
    /* set the value backend of the node to 'external value source' */
    UA_ValueBackend be_value;
    be_value.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
    be_value.backend.external.value = &gw_v_cfg->rt_value;
    be_value.backend.external.callback.userWrite = opc_rt_cb_ext_datawrite;
    be_value.backend.external.callback.notificationRead = \
        opc_rt_cb_ext_dataread_notif;
    UA_Server_setVariableNode_valueBackend(srv, v_node_id, be_value);
#else
    /* + add data source */
    UA_DataSource data_src;
    data_src.read = opc_fip_var_read;
    data_src.write = opc_fip_var_write;
    UA_Server_setVariableNode_dataSource(srv, v_node_id, data_src);
    UA_Server_setNodeContext(srv, v_node_id, (void *) v);
#endif

    return UA_STATUSCODE_GOOD;
}

/**
 * OPC - FIP data types
 */
static void opc_fip_dattype_define(UA_Server *srv)
{
    UA_UInt32 array_dims[1];

    /* => fip frame type (enum) */
    UA_DataTypeAttributes frm_attr = UA_DataTypeAttributes_default;
    frm_attr.description = UA_LOCALIZEDTEXT("en-US", "FipFrameTypeEnumeration");
    frm_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipFrameTypeEnumeration");
    UA_Server_addDataTypeNode(srv,
        UA_NODEID_STRING(1, "fip.dtype.frame"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ENUMERATION),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipFrameTypeEnumeration"),
        frm_attr, NULL, NULL);
    /* + add enumstrings property */
    UA_VariableAttributes pf_attr = UA_VariableAttributes_default;
    pf_attr.description = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pf_attr.displayName = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pf_attr.dataType = UA_TYPES[UA_TYPES_LOCALIZEDTEXT].typeId;
    pf_attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
    pf_attr.arrayDimensionsSize = 1;
    array_dims[0] = 0;
    pf_attr.arrayDimensions = &array_dims[0];
    UA_LocalizedText frm_data_contents[3];
    frm_data_contents[0] = UA_LOCALIZEDTEXT("", "Unknown");
    frm_data_contents[1] = UA_LOCALIZEDTEXT("", "FIP");
    frm_data_contents[2] = UA_LOCALIZEDTEXT("", "WorldFIP");
    UA_Variant_setArray(&pf_attr.value, frm_data_contents, 3,
        &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_NULL,
        UA_NODEID_STRING(1, "fip.dtype.frame"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
        UA_QUALIFIEDNAME(0, "EnumStrings"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE),
        pf_attr, NULL, NULL);

    /* => bitrate type (enum) */
    UA_DataTypeAttributes b_attr = UA_DataTypeAttributes_default;
    b_attr.description = UA_LOCALIZEDTEXT("en-US", "FipBitrateEnumeration");
    b_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipBitrateEnumeration");
    UA_Server_addDataTypeNode(srv,
        UA_NODEID_STRING(1, "fip.dtype.bitrate"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ENUMERATION),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipBitrateEnumeration"),
        b_attr, NULL, NULL);
    /* + add enumstrings property */
    UA_VariableAttributes pb_attr = UA_VariableAttributes_default;
    pb_attr.description = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pb_attr.displayName = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pb_attr.dataType = UA_TYPES[UA_TYPES_LOCALIZEDTEXT].typeId;
    pb_attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
    pb_attr.arrayDimensionsSize = 1;
    array_dims[0] = 0;
    pb_attr.arrayDimensions = &array_dims[0];
    UA_LocalizedText b_data_contents[7];
    b_data_contents[0] = UA_LOCALIZEDTEXT("", "Unknown");
    b_data_contents[1] = UA_LOCALIZEDTEXT("", "31.25Kbps");
    b_data_contents[2] = UA_LOCALIZEDTEXT("", "1Mbps");
    b_data_contents[3] = UA_LOCALIZEDTEXT("", "2.5Mbps");
    b_data_contents[4] = UA_LOCALIZEDTEXT("", "5Mbps");
    b_data_contents[5] = UA_LOCALIZEDTEXT("", "12.5Mbps");
    b_data_contents[6] = UA_LOCALIZEDTEXT("", "25Mbps");
    UA_Variant_setArray(&pb_attr.value, b_data_contents, 7,
        &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_NULL,
        UA_NODEID_STRING(1, "fip.dtype.bitrate"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
        UA_QUALIFIEDNAME(0, "EnumStrings"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE),
        pb_attr, NULL, NULL);

    /* => var type (enum) */
    UA_DataTypeAttributes v_attr = UA_DataTypeAttributes_default;
    v_attr.description = UA_LOCALIZEDTEXT("en-US", "FipVarTypeEnumeration");
    v_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipVarTypeEnumeration");
    UA_Server_addDataTypeNode(srv,
        UA_NODEID_STRING(1, "fip.dtype.var_type"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ENUMERATION),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipVarTypeEnumeration"),
        v_attr, NULL, NULL);
    /* + add enumstrings property */
    UA_VariableAttributes pv_attr = UA_VariableAttributes_default;
    pv_attr.description = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pv_attr.displayName = UA_LOCALIZEDTEXT("en-US", "EnumStrings");
    pv_attr.dataType = UA_TYPES[UA_TYPES_LOCALIZEDTEXT].typeId;
    pv_attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
    pv_attr.arrayDimensionsSize = 1;
    array_dims[0] = 0;
    pv_attr.arrayDimensions = &array_dims[0];
    UA_LocalizedText v_data_contents[3];
    v_data_contents[0] = UA_LOCALIZEDTEXT("", "Cons");
    v_data_contents[1] = UA_LOCALIZEDTEXT("", "Prod");
    v_data_contents[2] = UA_LOCALIZEDTEXT("", "Sync");
    UA_Variant_setArray(&pv_attr.value, v_data_contents, 3,
        &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_NULL,
        UA_NODEID_STRING(1, "fip.dtype.var_type"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
        UA_QUALIFIEDNAME(0, "EnumStrings"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE),
        pv_attr, NULL, NULL);

    /* => fip statistics (struct) */
    UA_DataTypeAttributes s_attr = UA_DataTypeAttributes_default;
    s_attr.description = UA_LOCALIZEDTEXT("en-US", "FipStatisticsSummaryDataType");
    s_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipStatisticsSummaryDataType");
    UA_Server_addDataTypeNode(srv,
        FipStatisticsSummaryType.typeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipStatisticsSummaryDataType"),
        s_attr, NULL, NULL);
}

/**
 * OPC - FIP variables types
 */
static void opc_fip_vartype_define(UA_Server *srv)
{
    UA_VariableTypeAttributes s_attr = UA_VariableTypeAttributes_default;
    s_attr.description = UA_LOCALIZEDTEXT("en-US", "FipStatisticsSummaryType");
    s_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipStatisticsSummaryType");
    s_attr.dataType = FipStatisticsSummaryType.typeId;
    s_attr.valueRank = UA_VALUERANK_SCALAR;

    FipStatisticsSummary stats;
    memset(&stats, 0, sizeof(stats));
    UA_Variant_setScalar(&s_attr.value, &stats, &FipStatisticsSummaryType);
    UA_Server_addVariableTypeNode(srv,
        UA_NODEID_STRING(1, "fip.vtype.stats"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipStatisticsSummaryType"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        s_attr, NULL, NULL);
}

/**
 * OPC - FIP Node object type
 */
static void opc_fip_objtype_node_define(UA_Server *srv)
{
    UA_String default_str = UA_STRING("Unknown");
    UA_Byte default_u8_val = 0;
    UA_UInt32 default_u32_val = 0;
    UA_Int32 default_i32_val = 0;
    FipStatisticsSummary default_stats;
    memset(&default_stats, 0, sizeof(FipStatisticsSummary));

    UA_ObjectTypeAttributes nt_attr = UA_ObjectTypeAttributes_default;
    nt_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipNodeType");
    UA_Server_addObjectTypeNode(srv,
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipNodeType"), nt_attr,
        NULL, NULL);

    /* node type => name (+ mandatory) */
    UA_VariableAttributes nt_name_attr = UA_VariableAttributes_default;
    nt_name_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node name");
    nt_name_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Name");
    nt_name_attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    nt_name_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_name_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_name_attr.value, &default_str,
        &UA_TYPES[UA_TYPES_STRING]);
    UA_Server_addVariableNode(srv,                              /* server */
        UA_NODEID_NULL,                                         /* requestedNewNodeId */
        UA_NODEID_STRING(1, "fip.otype.node"),                  /* parentNodeId */
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),            /* referenceTypeId */
        UA_QUALIFIEDNAME(1, "fip.name"),                        /* browseName */
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),    /* typeDefinition */
        nt_name_attr,                                           /* attr */
        NULL,                                                   /* nodeContext */
        NULL                                                    /* outNewNodeId */
        );
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.otype.node"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => address (+ mandatory) */
    UA_VariableAttributes nt_addr_attr = UA_VariableAttributes_default;
    nt_addr_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node address");
    nt_addr_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Address");
    nt_addr_attr.dataType = UA_TYPES[UA_TYPES_BYTE].typeId;
    nt_addr_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_addr_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_addr_attr.value, &default_u8_val,
        &UA_TYPES[UA_TYPES_BYTE]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.addr"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.addr"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_addr_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.addr"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => segment (+ mandatory) */
    UA_VariableAttributes nt_seg_attr = UA_VariableAttributes_default;
    nt_seg_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node segment");
    nt_seg_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Segment");
    nt_seg_attr.dataType = UA_TYPES[UA_TYPES_BYTE].typeId;
    nt_seg_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_seg_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_seg_attr.value, &default_u8_val,
        &UA_TYPES[UA_TYPES_BYTE]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.seg"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.seg"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_seg_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.seg"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => frame_type (+ mandatory) */
    UA_VariableAttributes nt_frm_type_attr = UA_VariableAttributes_default;
    nt_frm_type_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node frame type");
    nt_frm_type_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FrameType");
    nt_frm_type_attr.dataType = UA_NODEID_STRING(1, "fip.dtype.frame");
    nt_frm_type_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_frm_type_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_frm_type_attr.value, &default_i32_val,
        &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.frm_type"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.frm_type"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_frm_type_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.frm_type"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => bitrate (+ mandatory) */
    UA_VariableAttributes nt_bitrate_attr = UA_VariableAttributes_default;
    nt_bitrate_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node bitrate");
    nt_bitrate_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Bitrate");
    nt_bitrate_attr.dataType = UA_NODEID_STRING(1, "fip.dtype.bitrate");
    nt_bitrate_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_bitrate_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_bitrate_attr.value, &default_i32_val,
        &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.bitrate"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.bitrate"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_bitrate_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.bitrate"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => turn_around_us (+ mandatory) */
    UA_VariableAttributes nt_tr_attr = UA_VariableAttributes_default;
    nt_tr_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node turnaround time (us)");
    nt_tr_attr.displayName = UA_LOCALIZEDTEXT("en-US", "TurnAroundUs");
    nt_tr_attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    nt_tr_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_tr_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_tr_attr.value, &default_u32_val,
        &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.tr_ustime"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.tr_ustime"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_tr_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.tr_ustime"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* node type => silence_us (+ mandatory) */
    UA_VariableAttributes nt_ts_attr = UA_VariableAttributes_default;
    nt_ts_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP node silence time (us)");
    nt_ts_attr.displayName = UA_LOCALIZEDTEXT("en-US", "SilenceUs");
    nt_ts_attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    nt_ts_attr.valueRank = UA_VALUERANK_SCALAR;
    nt_ts_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&nt_ts_attr.value, &default_u32_val,
        &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.ts_ustime"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.ts_ustime"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        nt_ts_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.ts_ustime"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* => node type => statistics (+ mandatory) */
    UA_VariableAttributes n_stats_attr = UA_VariableAttributes_default;
    n_stats_attr.description = UA_LOCALIZEDTEXT("en-US", "FIP statistics");
    n_stats_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipStatistics");
    n_stats_attr.dataType = FipStatisticsSummaryType.typeId;
    n_stats_attr.valueRank = UA_VALUERANK_SCALAR;
    n_stats_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&n_stats_attr.value, &default_stats, &FipStatisticsSummaryType);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "fip.stats"),
        UA_NODEID_STRING(1, "fip.otype.node"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "fip.stats"),
        UA_NODEID_STRING(1, "fip.vtype.stats"),
        n_stats_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "fip.stats"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* add constructor */
    UA_NodeTypeLifecycle lifecycle;
    lifecycle.constructor = opc_fip_type_node_constructor;
    lifecycle.destructor = NULL;
    UA_Server_setNodeTypeLifecycle(srv,
        UA_NODEID_STRING(1, "fip.otype.node"), lifecycle);
}

/**
 * OPC - FIP Variable object type
 */
static void opc_fip_objtype_var_define(UA_Server *srv)
{
    UA_String default_str = UA_STRING("Unknown");
    UA_ByteString default_bstr_val = UA_BYTESTRING_NULL;
    UA_Boolean default_bool_val = false;
    UA_UInt16 default_u16_val = 0;
    UA_UInt32 default_u32_val = 0;
    UA_Int32 default_i32_val = 0;

    UA_ObjectTypeAttributes vt_attr = UA_ObjectTypeAttributes_default;
    vt_attr.displayName = UA_LOCALIZEDTEXT("en-US", "FipVarType");
    UA_Server_addObjectTypeNode(srv,
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "FipVarType"), vt_attr,
        NULL, NULL);

    /* var type => name (+ mandatory) */
    UA_VariableAttributes vt_name_attr = UA_VariableAttributes_default;
    vt_name_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable name");
    vt_name_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Name");
    vt_name_attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    vt_name_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_name_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_name_attr.value, &default_str,
        &UA_TYPES[UA_TYPES_STRING]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.name"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.name"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_name_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.name"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* var type => type (+ mandatory) */
    UA_VariableAttributes vt_type_attr = UA_VariableAttributes_default;
    vt_type_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable type");
    vt_type_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Type");
    vt_type_attr.dataType = UA_NODEID_STRING(1, "fip.dtype.var_type");
    vt_type_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_type_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_type_attr.value, &default_i32_val,
        &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.type"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.type"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_type_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.type"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* var type => id (+ mandatory) */
    UA_VariableAttributes vt_id_attr = UA_VariableAttributes_default;
    vt_id_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable identifier");
    vt_id_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Id");
    vt_id_attr.dataType = UA_TYPES[UA_TYPES_UINT16].typeId;
    vt_id_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_id_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_id_attr.value, &default_u16_val,
        &UA_TYPES[UA_TYPES_UINT16]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.id"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.id"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_id_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.id"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* var type => payload_bsz (+ mandatory) */
    UA_VariableAttributes vt_bsz_attr = UA_VariableAttributes_default;
    vt_bsz_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable payload byte size");
    vt_bsz_attr.displayName = UA_LOCALIZEDTEXT("en-US", "PayloadBsz");
    vt_bsz_attr.dataType = UA_TYPES[UA_TYPES_UINT16].typeId;
    vt_bsz_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_bsz_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_bsz_attr.value, &default_u16_val,
        &UA_TYPES[UA_TYPES_UINT16]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.payload_bsz"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.payload_bsz"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_bsz_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.payload_bsz"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* var type => refreshment */
    UA_VariableAttributes vt_ref_attr = UA_VariableAttributes_default;
    vt_ref_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable refreshment capability");
    vt_ref_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Refreshment");
    vt_ref_attr.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId;
    vt_ref_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_ref_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_ref_attr.value, &default_bool_val,
        &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.refreshment"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.refreshment"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_ref_attr, NULL, NULL);

    /* var type => refresh_us */
    UA_VariableAttributes vt_ref_us_attr = UA_VariableAttributes_default;
    vt_ref_us_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable refreshment time (us)");
    vt_ref_us_attr.displayName = UA_LOCALIZEDTEXT("en-US", "RefreshmentUs");
    vt_ref_us_attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    vt_ref_us_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_ref_us_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_ref_us_attr.value, &default_u32_val,
        &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.refreshment_ustime"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.refreshment_ustime"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_ref_us_attr, NULL, NULL);

    /* var type => promptness */
    UA_VariableAttributes vt_prompt_attr = UA_VariableAttributes_default;
    vt_prompt_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable promptness capability");
    vt_prompt_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Promptness");
    vt_prompt_attr.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId;
    vt_prompt_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_prompt_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_prompt_attr.value, &default_bool_val,
        &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.promptness"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.promptness"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_prompt_attr, NULL, NULL);

    /* var type => prompt_us */
    UA_VariableAttributes vt_prompt_us_attr = UA_VariableAttributes_default;
    vt_prompt_us_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable promptness time (us)");
    vt_prompt_us_attr.displayName = UA_LOCALIZEDTEXT("en-US", "PromptnessUs");
    vt_prompt_us_attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    vt_prompt_us_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_prompt_us_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_prompt_us_attr.value, &default_u32_val,
        &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.promptness_ustime"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.promptness_ustime"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_prompt_us_attr, NULL, NULL);

    /* var type => event (+ mandatory) */
    UA_VariableAttributes vt_event_attr = UA_VariableAttributes_default;
    vt_event_attr.description = UA_LOCALIZEDTEXT("en-US", "Variable synchronization capability");
    vt_event_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Event");
    vt_event_attr.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId;
    vt_event_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_event_attr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vt_event_attr.value, &default_bool_val,
        &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.event"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.event"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_event_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.event"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* var type => data (+ mandatory) */
    UA_VariableAttributes vt_data_attr = UA_VariableAttributes_default;
    vt_data_attr.description = UA_LOCALIZEDTEXT("en-US", "Data");
    vt_data_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Data");
    vt_data_attr.dataType = UA_TYPES[UA_TYPES_BYTESTRING].typeId;
    vt_data_attr.valueRank = UA_VALUERANK_SCALAR;
    vt_data_attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&vt_data_attr.value, &default_bstr_val,
        &UA_TYPES[UA_TYPES_BYTESTRING]);
    UA_Server_addVariableNode(srv,
        UA_NODEID_STRING(1, "var.data"),
        UA_NODEID_STRING(1, "fip.otype.var"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, "var.data"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vt_data_attr, NULL, NULL);
    UA_Server_addReference(srv, UA_NODEID_STRING(1, "var.data"),
       UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
       UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    /* add constructor */
    UA_NodeTypeLifecycle lifecycle;
    lifecycle.constructor = opc_fip_type_var_constructor;
    lifecycle.destructor = NULL;
    UA_Server_setNodeTypeLifecycle(srv,
        UA_NODEID_STRING(1, "fip.otype.var"), lifecycle);
}

void opc_fip_types_init(UA_Server *srv)
{
    /* add fip data types */
    opc_fip_dattype_define(srv);
    /* add fip var types */
    opc_fip_vartype_define(srv);
    /* add fip object types */
    opc_fip_objtype_node_define(srv);
    opc_fip_objtype_var_define(srv);
}
