I've noticed that OPC connections in v8.3 will not coerce java Long values (from jython int) to any unsigned OPC UA types, like UInt32. Is this deliberate?
W [c.i.i.g.o.c.c.OpcUaConnection ] [20:20:00.647]: write failed: unsupported scalar conversion: target=UInt32, value=class java.lang.Long connection-name=Ignition OPC UA Server
2025/03/27 20:20:00.656 | java.lang.RuntimeException: unsupported scalar conversion: target=UInt32, value=class java.lang.Long
2025/03/27 20:20:00.656 | at com.inductiveautomation.ignition.gateway.opcua.IgnitionToOpcUa.ignitionToOpcUaScalar$opc_ua_gateway(OpcUaConversions.kt:245)
....
It looks like in 8.1 the coercion would fail (silently) and return null, at which point the write would just pass on the "raw" uncoerced value that is almost certainly the wrong type to the OPC UA server. A correct implementation on the server side would reject it with Bad_TypeMismatch at that point.
It didn't wrap though, it just passes the wrong kind of value to the server. OPC UA servers under no circumstance are allowed to accept a value of the wrong datatype or perform coercions on it.
It's a bug (and tested by the compliance tool) to accept a write with a value that isn't of the type advertised by the DataType attribute. If you want to accept any ol' crap you have to use Variant for your DataType, which sucks for usability.
Anyway, most of this conversion logic is pretty much new for 8.3, it's still WIP in some areas, so I'll keep this in mind and see if there's anything that makes more sense.
Ok, noted, but that's just bad behavior in our drivers.
If you try it against e.g. the demo server, which is actually doing the right thing, you see this in 8.1:
[Good]
[Bad("Bad_TypeMismatch: The value supplied for the attribute is not of the same type as the attribute's value.")]
[Bad("Bad_TypeMismatch: The value supplied for the attribute is not of the same type as the attribute's value.")]
>>>
I would wager the internal servers in Siemens and other PLCs also do not tolerate this.
I think I'm going to change this so it overflows / rolls over on the client side.
I've really never liked the idea of silent rollover, but that's how signed types have always behaved, so I suppose unsigned types should behave similar. This is also technically a breaking change from 8.1, but it seems like more intuitive behavior.
In other words, purely an artifact of Ignition's OPC value source? I like this. And it will let me prune some to-be-unnecessary coercions in my drivers.
FWIW, in the C standard, it is the unsigned types that are required to roll over, and the signed types that generally do so as "expected" but undefined behavior.
This approach also resolves the fundamental lack of unsigned types in jython. Not having to construct unsigned list/array elements is a big win.
Yeah you would be able to remove these specific coercions in your drivers. This logic all lives kind of in the write logic for the OPC UA connections - we read the datatype, value rank, etc... of the Node being written to, and then make an effort to convert to the type required by OPC UA from whatever value came from the Ignition side, which can be all kinds of things - boolean to numeric types, JSON primitives, signed types when the target is unsigned, lists when the target is an array, etc...
Can you give me a heads-up when this hits the nightlies? I realized that I cannot easily do the necessary masking in my current QA script, as the random value generation can only see the tag datatype on the Ignition side, not the OPC node's datatype.
Further note. The write operation that triggers this flaw yields a logged exception describing the problem, but the list of returned status codes for the batch of writes are allBad_Failure, even though the write items with unaffected data types actually succeed.