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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user