fix(gateway): adapt DataValue construction to asyncua 2.x

main's requirements.txt unpins asyncua (PR #30), so CI installs 2.0.1,
where DataValue's field was renamed StatusCode_ -> StatusCode. The old
kwarg made every OPC write raise TypeError at runtime - masked locally
by a stale asyncua 1.0.6 in the dev conda env. Also uses ua.DateTime
(a datetime subclass) and ua.UInt32 to satisfy the new stricter types,
with no behavior change (verified via binary encode/decode round-trip).
This commit is contained in:
PedroHMCosme
2026-09-22 12:02:29 -03:00
parent 4f4037b4ae
commit 673b13cc6e
4 changed files with 11 additions and 12 deletions

View File

@@ -89,7 +89,7 @@ class ActiveReadEngine(SientiaMonitoring):
@staticmethod @staticmethod
def _extract(dv): def _extract(dv):
status_name = dv.StatusCode_.name if dv.StatusCode_ is not None else None status_name = dv.StatusCode.name if dv.StatusCode is not None else None
return dv.Value.Value, status_name, dv.SourceTimestamp, dv.ServerTimestamp return dv.Value.Value, status_name, dv.SourceTimestamp, dv.ServerTimestamp
@staticmethod @staticmethod

View File

@@ -7,7 +7,6 @@ to the server, a rate-limit floor between writes, and OPC error typing.
import asyncio import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from asyncua import ua from asyncua import ua
from asyncua.ua.uaerrors import UaStatusCodeError from asyncua.ua.uaerrors import UaStatusCodeError
@@ -146,12 +145,12 @@ class WriteEngine(SientiaMonitoring):
variant = ua.Variant(typed_value, self._variant_type(cmd['data_type'])) variant = ua.Variant(typed_value, self._variant_type(cmd['data_type']))
write_quality = cmd.get('write_quality', self.write_quality) write_quality = cmd.get('write_quality', self.write_quality)
write_timestamp = cmd.get('write_timestamp', self.write_timestamp) write_timestamp = cmd.get('write_timestamp', self.write_timestamp)
# NOTE: DataValue.StatusCode_ default_factory populates a Good status # NOTE: DataValue.StatusCode default_factory populates a Good status
# even when omitted, so "off" must pass StatusCode_=None explicitly # even when omitted, so "off" must pass StatusCode=None explicitly
# or the flag silently no-ops. # or the flag silently no-ops.
status_code = ua.StatusCode(ua.StatusCodes.Good) if write_quality else None status_code = ua.StatusCode(ua.UInt32(ua.StatusCodes.Good)) if write_quality else None
source_ts = datetime.fromisoformat(cmd['timestamp']) if write_timestamp else None source_ts = ua.DateTime.fromisoformat(cmd['timestamp']) if write_timestamp else None
return ua.DataValue(variant, StatusCode_=status_code, SourceTimestamp=source_ts) return ua.DataValue(variant, StatusCode=status_code, SourceTimestamp=source_ts)
def _is_session_bad(self, exc: Exception) -> bool: def _is_session_bad(self, exc: Exception) -> bool:
return isinstance(exc, UaStatusCodeError) and type(exc).__name__ in _SESSION_BAD_NAMES return isinstance(exc, UaStatusCodeError) and type(exc).__name__ in _SESSION_BAD_NAMES

View File

@@ -25,7 +25,7 @@ def make_dv(value=42.0, status='Good', source_ts=None, server_ts=None):
server_ts = server_ts or source_ts server_ts = server_ts or source_ts
return ua.DataValue( return ua.DataValue(
ua.Variant(value), ua.Variant(value),
StatusCode_=ua.StatusCode(getattr(ua.StatusCodes, status)), StatusCode=ua.StatusCode(getattr(ua.StatusCodes, status)),
SourceTimestamp=source_ts, SourceTimestamp=source_ts,
ServerTimestamp=server_ts, ServerTimestamp=server_ts,
) )

View File

@@ -108,8 +108,8 @@ async def test_write_defaults_carry_statuscode_and_timestamp():
await eng.write(base_cmd()) await eng.write(base_cmd())
dv = node.write_value.await_args.args[0] dv = node.write_value.await_args.args[0]
assert dv.StatusCode_ is not None assert dv.StatusCode is not None
assert dv.StatusCode_.is_good() assert dv.StatusCode.is_good()
assert dv.SourceTimestamp == datetime.fromisoformat('2026-07-13T12:00:00Z') assert dv.SourceTimestamp == datetime.fromisoformat('2026-07-13T12:00:00Z')
@@ -122,7 +122,7 @@ async def test_write_engine_flags_off_omit_statuscode_and_timestamp():
await eng.write(base_cmd()) await eng.write(base_cmd())
dv = node.write_value.await_args.args[0] dv = node.write_value.await_args.args[0]
assert dv.StatusCode_ is None assert dv.StatusCode is None
assert dv.SourceTimestamp is None assert dv.SourceTimestamp is None
@@ -137,7 +137,7 @@ async def test_per_message_flag_override_disables_timestamp():
dv = node.write_value.await_args.args[0] dv = node.write_value.await_args.args[0]
assert dv.SourceTimestamp is None assert dv.SourceTimestamp is None
assert dv.StatusCode_ is not None # write_quality untouched, stays on assert dv.StatusCode is not None # write_quality untouched, stays on
@pytest.mark.asyncio @pytest.mark.asyncio