Troubleshooting contrib-opcua-server String Writes

Daniel Price7 min read
OPC / OPC UAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

A client writes "b" to the String variable, emits two warning messages, and then receives BadInternalError (0x80020000) on readback. Two separate faults are involved: the server setter converts the String to a number, while one client-node version emits warnings even when the write succeeds.

How does the write travel from client to variable?

Follow the packet from the Node-RED inject node to the OPC UA address space. The inject node supplies msg.payload; the OpcUa-Item node adds the target NodeId and datatype; the OpcUa-Client issues the Write service request; and the compact server invokes the variable's setter. A later Read request invokes the getter and returns a Variant to the client.

Path element Configured value Diagnostic significance
Write payload "b" A valid JavaScript string entering the write path
Item NodeId ns=1;s=count Must match the server variable exactly
Item datatype String Matches the address-space datatype
Endpoint opc.tcp://localhost:4840 Uses the local host loopback path
Security policy and mode None No certificate or secure-channel policy mismatch is involved
Server access Anonymous allowed No username authentication blocks the request
Read-client time setting Controls the configured read activity timing
Write-client time setting Recorded write-node timing setting; it does not repair a datatype mismatch
Server initialization delay Requests must occur after address-space construction

Layer one is short in this installation because both endpoints use localhost. There is no external cable, switch, or routed hop between the client and server. The successful initial read and successful numeric-variable operations also show that the endpoint, TCP connection, OPC UA session, NodeId routing, and basic service path work. The failure begins after the String setter receives the Variant.

Which symptoms identify each failure point?

Symptom Failure point Deciding test
Initialized String reads correctly Getter and initial backing value are valid Read before making a write
Integer and Double variables read and write correctly Transport and general client/server operation are functional Compare service results through the same endpoint
Read fails after writing "b" String backing value was corrupted by the setter Inspect the value assigned inside set
BadInternalError (0x80020000) appears during active reading The getter cannot return a valid String Variant from the stored value Remove numeric conversion and repeat write/readback
Counter: Second set value by Input b Client-node input processing warning Check the Write result and server readback separately
Payload warning shows a serialized message structure Client-node diagnostic behavior rather than proof of an OPC UA write failure Read the target directly after the write
Warnings appear in 0.2.284 but not 0.2.248 Version-dependent warning behavior Run the same nodes and payload under both versions

The read error and client warnings must not be treated as one event. Removing parseFloat() restores readable server data. Separately, the warnings can remain while correct values are written. A Node-RED warning is not an OPC UA StatusCode; judge the transaction by its service result and readback value.

Which correction approach fits this case?

Approach What it changes When to use it Limitation
Correct the server setter Stores the incoming String without numeric conversion Required whenever the variable is declared String Does not necessarily remove version-specific client warnings
Align the client item definition Targets ns=1;s=count with datatype String Required for every write path to this variable The shown flow is already aligned
Verify and tolerate the warning Leaves the installed client-node version unchanged Use only when the Write result is good and independent readback equals the sent string Warnings may obscure a later real fault
Use version 0.2.248 Removes the reported warnings for the same nodes Use when warning-free operation is operationally necessary and version change is acceptable It does not correct a server setter that converts Strings to numbers

Correct the setter first. Then verify the write under the installed version. If readback is correct but 0.2.284 continues to emit the two messages, classify them as version-specific diagnostics. Moving to 0.2.248 is a warning-suppression workaround, not the datatype fix.

Why does parseFloat() break the String variable?

The variable declares dataType: "String", and its getter constructs a Variant with DataType.String. That establishes a contract: the backing variable count must remain a string.

The original setter violates that contract:

set: function(variant) {
    count = parseFloat(variant.value);
    return opcua.StatusCodes.Good;
}

For input "b", JavaScript parseFloat() produces NaN. The setter nevertheless returns Good, so the client can see an apparently successful write even though the backing value is no longer valid for a String Variant. On the next read, the getter labels that non-string value as DataType.String, and the server surfaces BadInternalError (0x80020000).

A numeric-looking string also remains the wrong design. Converting "2" produces the number 2, not the string "2". The getter's declared datatype and the stored JavaScript value still disagree. A server must not acknowledge a write as Good after silently changing the value into an incompatible type.

How should the address-space variable be corrected?

Keep the getter and setter symmetrical: accept a String Variant, store its value as a string, and return it as a String Variant. The direct correction is:

let count = "a";

namespace.addVariable({
    "componentof": Tanks,
    "nodeId": "ns=1;s=count",
    "browseName": "Counter",
    "dataType": "String",
    "value": {
        "get": function() {
            return new Variant({
                "dataType": DataType.String,
                "value": count
            });
        },
        "set": function(variant) {
            count = variant.value;
            return opcua.StatusCodes.Good;
        }
    }
});
  1. Remove parseFloat() from the setter.
  2. Retain let count = "a"; so the initial backing value is a string.
  3. Retain dataType: "String" in the variable declaration.
  4. Retain DataType.String in the returned Variant.
  5. Deploy or restart the flow so the address space is reconstructed with the corrected setter.
  6. Wait until address-space construction completes before issuing the first test read; the compact server is configured with a initialization delay.

If the application actually requires numeric storage, declare and handle a numeric OPC UA datatype throughout instead of presenting the node as a String. Conversion belongs at a defined application boundary, not inside a setter whose public datatype is String.

How should the client write path be configured?

The supported write path uses an inject node, an OpcUa-Item, and an OpcUa-Client configured for write. The item node carries the address and datatype metadata; the inject node carries the new value.

  1. Set the inject payload type to string and enter b as the payload.
  2. Set the item to ns=1;s=count.
  3. Set the item datatype to String.
  4. Connect the item to the client whose action is write.
  5. Use endpoint opc.tcp://localhost:4840 with the configured None security policy and mode.
  6. Attach a full-message debug node to the write client so the service result can be separated from warning text.
  7. Use a separate read client and item for the same NodeId, then display its payload in a debug node.

The message that includes an array of message fields and values is generated while the client node interprets its input. It does not by itself establish that the server stored that array. Inspect the OPC UA write status, then read ns=1;s=count through the server. If the returned value is "b", the data path completed despite the warning.

What proves that the repair is complete?

  1. Restart the corrected server and read ns=1;s=count before writing. Confirm that the initialized value is "a".
  2. Inject the string "b" through the write path.
  3. Check that the write operation does not return a bad OPC UA service result.
  4. Read the same NodeId immediately and confirm that the payload is exactly "b", not a number, NaN, or a serialized Node-RED message.
  5. Allow the configured read activity to repeat and confirm that BadInternalError (0x80020000) does not recur.
  6. Write a numeric-looking string such as "2" and confirm readback remains a String value rather than being converted to a number.
  7. If warnings remain under 0.2.284, repeat the identical write and readback under 0.2.248. Treat the version change as validated only when the warning disappears without changing the stored value.

FAQ

What happens if I use parseFloat() in an OPC UA String setter?

"b" becomes NaN, while "2" becomes the number 2. Both violate the backing-value contract for a variable returned as DataType.String, and the next read can fail with BadInternalError (0x80020000).

What happens if version 0.2.284 warns but the value is written?

Judge the operation by the OPC UA service result and an independent read of ns=1;s=count. The same nodes were reported to run without those warnings under 0.2.248, so changing version is an option when warning-free logs are required.

How do I verify an OPC UA String write?

Write "b" with the item datatype set to String, then read the same NodeId through a separate read path. The final verification step is confirming repeated readback equals "b" and no longer returns BadInternalError (0x80020000).

Back to blog