[
    {
        "id": "0ca41155c5f07e18",
        "type": "tab",
        "label": "OPCUA Server - TemperatureSensor",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "44b39231a044d020",
        "type": "function",
        "z": "0ca41155c5f07e18",
        "name": "TemperatureSensor",
        "func": "const sterfive = global.get(\"sterfive\");\nif (!sterfive) {\n    node.error(\"global.get('sterfive') is not set — is opcua-for-nodered loaded?\");\n} else {\n\n    node.status({fill:\"blue\",shape:\"ring\",text:\"configuring ...\"});\n    const { opcua, bootstrap } = sterfive;\n    const { bootstrapServer } = bootstrap;\n\n    const cfg = msg.config || {};\n\n    const handle = await bootstrapServer({\n        port: cfg.port ?? 4840,\n        endpoint: cfg.endpoint || \"node-red-server\",\n        forceRebuild: true,\n        nodesets: [\n            \"standard\",\n            \"di\",\n            \"ia\",\n            \"machinery\"\n        ],\n        onPopulate: (addressSpace, exposed) => {\n            createTemperatorSensorType(opcua,addressSpace);\n            const { setRawValue } = instantiateTemperatureSensor(opcua, addressSpace);\n            exposed.setRawValue = setRawValue;\n          node.status({fill:\"orange\",shape:\"ring\",text:\"configured ...\"});\n       },\n    });\n    \n \n    flow.set(\"sterfive$onRawTemperatureChange\", handle.exposed.setRawValue);\n    const a = flow.get(\"sterfive$onRawTemperatureChange\");\n    node.send({ \n       payload: \n      `OPC UA Server (globals path) running at ${handle.server.getEndpointUrl()}`\n    });\n \n    if (handle.isRunning) {\n       node.status({fill:\"green\",shape:\"ring\",text:\"listening ...\"});\n    }\n\n}\n\nfunction createTemperatorSensorType(opcua, addressSpace) {\n    const namespace = addressSpace.getOwnNamespace();\n    const nsDi = addressSpace.getNamespaceIndex(\"http://opcfoundation.org/UA/DI/\");\n    if (nsDi === -1) {\n        throw new Error(\"DI namespace not found in address space\");\n    }\n    const componentType = addressSpace.findObjectType(\"DeviceType\", nsDi);\n    if (!componentType) {\n        throw new Error(\"ComponentType not found in DI namespace\");\n    }\n    const temperatureSensorType = namespace.addObjectType({\n        browseName: \"TemperatureSensorType\",\n        subtypeOf: componentType,\n        eventNotifier: 0x01, // This object can generate events\n    });\n    const parameterSet = namespace.addObject({\n        browseName: { name: \"ParameterSet\", namespaceIndex: nsDi },\n        componentOf: temperatureSensorType,\n        modellingRule: \"Mandatory\",\n    });\n    const _uaTemperature = namespace.addAnalogDataItem({\n        browseName: \"Temperature\",\n        componentOf: parameterSet,\n        dataType: \"Double\",\n        minimumSamplingInterval: 500,\n        engineeringUnitsRange: { low: -50, high: 100 },\n        engineeringUnits: opcua.standardUnits.degree_celsius,\n        modellingRule: \"Mandatory\",\n    });\n    namespace.addVariable({\n        browseName: { name: \"CalibrationDate\", namespaceIndex: namespace.index },\n        dataType: \"DateTime\",\n        componentOf: parameterSet,\n        modellingRule: \"Mandatory\",\n    });\n    namespace.addVariable({\n        browseName: { name: \"SensorStatus\", namespaceIndex: namespace.index },\n        dataType: \"Int32\",\n        componentOf: parameterSet,\n        modellingRule: \"Optional\",\n    });\n    const methodSet = namespace.addObject({\n        browseName: { name: \"MethodSet\", namespaceIndex: nsDi },\n        componentOf: temperatureSensorType,\n        modellingRule: \"Mandatory\",\n    });\n    namespace.addMethod(methodSet, {\n        browseName: { name: \"Calibrate\", namespaceIndex: namespace.index },\n        modellingRule: \"Mandatory\",\n        inputArguments: [\n            {\n                name: \"CoefficientA\",\n                dataType: opcua.DataType.Double,\n            },\n            {\n                name: \"CoefficientB\",\n                dataType: opcua.DataType.Double,\n            },\n            // T = a * raw + b\n        ],\n        outputArguments: [\n            {\n                name: \"Success\",\n                dataType: opcua.DataType.Boolean,\n            },\n        ],\n    });\n}\nconst HealthStatus = {\n    Normal: 0,\n    Failure: 1,\n    CheckFunction: 2,\n    OffSpec: 3,\n    MaintenanceRequired: 4,\n};\nfunction instantiateTemperatureSensor(opcua, addressSpace) {\n\n    const namespace = addressSpace.getOwnNamespace();\n    const ownNamespaceIndex = namespace.index;\n    const temperationSensorType = addressSpace.findObjectType(\"TemperatureSensorType\", ownNamespaceIndex);\n    if (!temperationSensorType) {\n        throw new Error(\"TemperatureSensorType not found in address space\");\n    }\n    const nsDi = addressSpace.getNamespaceIndex(\"http://opcfoundation.org/UA/DI/\");\n    if (nsDi === -1) {\n        throw new Error(\"DI namespace not found in address space\");\n    }\n    const deviceSetFolder = addressSpace.findNode(`ns=${nsDi};i=5001`); // DeviceSet folder in DI namespace\n    if (!deviceSetFolder) {\n        // DeviceSet folder in DI namespace\n        throw new Error(\"DeviceSet folder not found in DI namespace\");\n    }\n    const uaTemperatureSensor = temperationSensorType.instantiate({\n        browseName: \"MyTemperatureSensor\",\n        organizedBy: deviceSetFolder, // Assuming deviceSetFolder is defined in the address space\n        eventSourceOf: addressSpace.rootFolder.objects.server, // Make the sensor an event source\n      \n    });\n    let coefA = 1;\n    let coefB = 0;\n    let rawValue = 0;\n    const methodSet = uaTemperatureSensor.getComponentByName(\"MethodSet\", nsDi);\n    if (!methodSet) {\n        throw new Error(\"MethodSet not found in TemperatureSensorType\");\n    }\n    const calibrateMethod = methodSet.getChildByName(\"Calibrate\");\n    if (!calibrateMethod) {\n        throw new Error(\"Calibrate method not found in MethodSet\");\n    }\n    const deviceHealth = uaTemperatureSensor.getComponentByName(\"DeviceHealth\", nsDi);\n    if (deviceHealth) {\n        deviceHealth.setValueFromSource({ dataType: opcua.DataType.Int32, value: 0 }); // 0 = Good\n    }\n    // need calibration\n    deviceHealth?.setValueFromSource({ dataType: opcua.DataType.Int32, value: HealthStatus.MaintenanceRequired });\n    const parameterSet = uaTemperatureSensor.getComponentByName(\"ParameterSet\", nsDi);\n    if (!parameterSet) {\n        throw new Error(\"ParameterSet not found in TemperatureSensorType\");\n    }\n    const calibrationDate = parameterSet.getChildByName(\"CalibrationDate\", 1);\n    calibrationDate?.setValueFromSource({ dataType: opcua.DataType.DateTime, value: new Date() });\n    calibrateMethod.bindMethod(async (inputArguments, context) => {\n        // ensure that the current user has the necessary permissions to execute this method\n        if (!context?.session?.getSessionId()) {\n            return { statusCode: opcua.StatusCodes.BadUserAccessDenied };\n        }\n        const coefficientA = inputArguments[0]?.value || 1.0;\n        const coefficientB = inputArguments[1]?.value || 0.0;\n        // Here you would implement the actual calibration logic using the coefficients\n        console.log(`Calibrating sensor with CoefficientA: ${coefficientA}, CoefficientB: ${coefficientB}`);\n        // For demonstration, we'll just return success without doing anything\n        coefA = coefficientA;\n        coefB = coefficientB;\n        setRawValue(rawValue); // Recalculate the temperature with the new coefficients\n        const success = true;\n        deviceHealth?.setValueFromSource({ dataType: opcua.DataType.Int32, value: HealthStatus.Normal });\n        calibrationDate?.setValueFromSource({ dataType: opcua.DataType.DateTime, value: new Date() });\n        return {\n            statusCode: opcua.StatusCodes.Good,\n            outputArguments: [{ dataType: opcua.DataType.Boolean, value: success }],\n        };\n    });\n    const uaTemperatureVariable = parameterSet\n        .getChildByName(\"Temperature\", 1);\n    if (!uaTemperatureVariable) {\n        throw new Error(\"Temperature variable not found in ParameterSet\");\n    }\n    const setRawValue = (newRawValue) => {\n        rawValue = newRawValue;\n        const calibratedValue = coefA * rawValue + coefB;\n        uaTemperatureVariable?.setValueFromSource({ dataType: opcua.DataType.Double, value: calibratedValue });\n    };\n    const exclusiveLimitAlarmType = addressSpace.findEventType(\"ExclusiveLimitAlarmType\");\n    if (!exclusiveLimitAlarmType) {\n        throw new Error(\"cannot find ExclusiveLimitAlarmType in namespace 0\");\n    }\n    namespace.instantiateExclusiveLimitAlarm(exclusiveLimitAlarmType, {\n        browseName: \"TemperatureConddition\",\n        componentOf: uaTemperatureSensor,\n        conditionName: \"TemperatureCondition\",\n        conditionSource: uaTemperatureSensor,\n        highHighLimit: 50,\n        highLimit: 40,\n        inputNode: uaTemperatureVariable, // the variable that will be monitored for change\n        lowLimit: 10,\n        setpointNode: null,\n    });\n    return { setRawValue };\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 140,
        "wires": [
            [
                "f713e3745e933393"
            ]
        ]
    },
    {
        "id": "d57386a79225e964",
        "type": "inject",
        "z": "0ca41155c5f07e18",
        "name": "Start Server",
        "props": [
            {
                "p": "config",
                "v": "{ \"port\": 26550 }",
                "vt": "json"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 210,
        "y": 140,
        "wires": [
            [
                "44b39231a044d020"
            ]
        ]
    },
    {
        "id": "f713e3745e933393",
        "type": "debug",
        "z": "0ca41155c5f07e18",
        "name": "debug output",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 770,
        "y": 180,
        "wires": []
    },
    {
        "id": "035721b7e2d4bef5",
        "type": "function",
        "z": "0ca41155c5f07e18",
        "name": "Update Raw Temperature",
        "func": "const setRawValue = flow.get(\"sterfive$onRawTemperatureChange\");\nif (!setRawValue) return;\n\ntry {\n    const rawValue = Math.sin(Date.now() / 10000) * 30 + 40;\n    setRawValue(rawValue);\n} catch(err) {\n    return { ...msg, payload: \"Errro=\" + err.message};\n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 450,
        "y": 240,
        "wires": [
            [
                "f713e3745e933393"
            ]
        ]
    },
    {
        "id": "cbde8d32bfca9fc0",
        "type": "inject",
        "z": "0ca41155c5f07e18",
        "name": "Simulate",
        "props": [],
        "repeat": "0.5",
        "crontab": "",
        "once": false,
        "onceDelay": "2",
        "topic": "",
        "x": 200,
        "y": 240,
        "wires": [
            [
                "035721b7e2d4bef5"
            ]
        ]
    }
]
