Merge branch 'feature/SIENTIAPDE-1972-gateway-write-read' into feature/SIENTIAPDE-1972-gateway-wiring

This commit is contained in:
PedroHMCosme
2026-09-22 12:03:57 -03:00
5 changed files with 14 additions and 13 deletions

View File

@@ -107,7 +107,9 @@ class OpcConnectionPool(SientiaMonitoring):
async def close_all(self):
"""Disconnect every pooled client (e.g. on shutdown)."""
for server_id in list(self._connections):
# list() snapshots the keys: _evict() pops from self._connections,
# so iterating the dict directly would raise "changed size during iteration".
for server_id in list(self._connections): # NOSONAR
await self._evict(server_id)
async def _evict(self, server_id: str):

View File

@@ -89,7 +89,7 @@ class ActiveReadEngine(SientiaMonitoring):
@staticmethod
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
@staticmethod

View File

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

View File

@@ -108,8 +108,8 @@ async def test_write_defaults_carry_statuscode_and_timestamp():
await eng.write(base_cmd())
dv = node.write_value.await_args.args[0]
assert dv.StatusCode_ is not None
assert dv.StatusCode_.is_good()
assert dv.StatusCode is not None
assert dv.StatusCode.is_good()
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())
dv = node.write_value.await_args.args[0]
assert dv.StatusCode_ is None
assert dv.StatusCode 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]
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