Skip to content

Commit f65ebe3

Browse files
authored
Merge branch 'main' into feature/loose-numpy-dependency
2 parents d4458de + 835efa6 commit f65ebe3

File tree

2 files changed

+249
-137
lines changed

2 files changed

+249
-137
lines changed

inference/enterprise/workflows/enterprise_blocks/sinks/opc_writer/v1.py

Lines changed: 105 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@
44
from functools import partial
55
from typing import List, Literal, Optional, Tuple, Type, Union
66

7+
from asyncua import ua
78
from asyncua.client import Client as AsyncClient
89
from asyncua.sync import Client, sync_async_client_method
10+
from asyncua.ua import VariantType
911
from asyncua.ua.uaerrors import BadNoMatch, BadTypeMismatch, BadUserAccessDenied
1012
from fastapi import BackgroundTasks
1113
from pydantic import ConfigDict, Field
1214

15+
16+
class UnsupportedTypeError(Exception):
17+
"""Raised when an unsupported value type is specified"""
18+
19+
pass
20+
21+
1322
from inference.core.workflows.execution_engine.entities.base import OutputDefinition
1423
from inference.core.workflows.execution_engine.entities.types import (
1524
BOOLEAN_KIND,
@@ -171,11 +180,25 @@ class BlockManifest(WorkflowBlockManifest):
171180
)
172181
value_type: Union[
173182
Selector(kind=[STRING_KIND]),
174-
Literal["Boolean", "Float", "Integer", "String"],
183+
Literal[
184+
"Boolean",
185+
"Double",
186+
"Float",
187+
"Int16",
188+
"Int32",
189+
"Int64",
190+
"Integer",
191+
"SByte",
192+
"String",
193+
"UInt16",
194+
"UInt32",
195+
"UInt64",
196+
],
175197
] = Field(
176198
default="String",
177-
description="The type of the value to be written to the target variable on the OPC UA server.",
178-
examples=["Boolean", "Float", "Integer", "String"],
199+
description="The type of the value to be written to the target variable on the OPC UA server. "
200+
"Supported types: Boolean, Double, Float, Int16, Int32, Int64, Integer (Int64 alias), SByte, String, UInt16, UInt32, UInt64.",
201+
examples=["Boolean", "Double", "Float", "Int32", "Int64", "String"],
179202
json_schema_extra={
180203
"always_visible": True,
181204
},
@@ -256,7 +279,20 @@ def run(
256279
object_name: str,
257280
variable_name: str,
258281
value: Union[str, bool, float, int],
259-
value_type: Literal["Boolean", "Float", "Integer", "String"] = "String",
282+
value_type: Literal[
283+
"Boolean",
284+
"Double",
285+
"Float",
286+
"Int16",
287+
"Int32",
288+
"Int64",
289+
"Integer",
290+
"SByte",
291+
"String",
292+
"UInt16",
293+
"UInt32",
294+
"UInt64",
295+
] = "String",
260296
timeout: int = 2,
261297
fire_and_forget: bool = True,
262298
disable_sink: bool = False,
@@ -285,28 +321,16 @@ def run(
285321
"message": "Sink cooldown applies",
286322
}
287323

288-
value_str = str(value)
289-
logging.debug(f"OPC Writer converting value '{value_str}' to type {value_type}")
290-
try:
291-
if value_type in [BOOLEAN_KIND, "Boolean"]:
292-
decoded_value = value_str.strip().lower() in ("true", "1")
293-
elif value_type in [FLOAT_KIND, "Float"]:
294-
decoded_value = float(value_str)
295-
elif value_type in [INTEGER_KIND, "Integer"]:
296-
decoded_value = int(value_str)
297-
elif value_type in [STRING_KIND, "String"]:
298-
decoded_value = value_str
299-
else:
300-
raise ValueError(f"Unsupported value type: {value_type}")
301-
logging.debug(f"OPC Writer successfully converted value to {decoded_value}")
302-
except ValueError as exc:
303-
logging.error(f"OPC Writer failed to convert value: {exc}")
304-
return {
305-
"disabled": False,
306-
"error_status": True,
307-
"throttling_status": False,
308-
"message": f"Failed to convert value: {exc}",
309-
}
324+
if value_type in [BOOLEAN_KIND, "Boolean"] and isinstance(value, str):
325+
# handle boolean conversion explicitly if value is a string
326+
decoded_value = value.strip().lower() in ("true", "1")
327+
else:
328+
# Use value directly - OPC UA library will convert based on type specification
329+
decoded_value = value
330+
331+
logging.debug(
332+
f"OPC Writer prepared value '{decoded_value}' for type {value_type}"
333+
)
310334

311335
opc_writer_handler = partial(
312336
opc_connect_and_write_value,
@@ -317,6 +341,7 @@ def run(
317341
object_name=object_name,
318342
variable_name=variable_name,
319343
value=decoded_value,
344+
value_type=value_type,
320345
timeout=timeout,
321346
node_lookup_mode=node_lookup_mode,
322347
)
@@ -376,6 +401,18 @@ def safe_disconnect(client: Client) -> None:
376401
logging.debug(f"OPC Writer disconnect error (non-fatal): {exc}")
377402

378403

404+
def get_node_data_type(var) -> str:
405+
"""
406+
Get the data type of an OPC UA node.
407+
Returns a string representation of the type, or "Unknown" if unable to read.
408+
"""
409+
try:
410+
return str(var.read_data_type_as_variant_type())
411+
except Exception as exc:
412+
logging.info(f"Unable to read node data type: {exc}")
413+
return "Unknown"
414+
415+
379416
def opc_connect_and_write_value(
380417
url: str,
381418
namespace: str,
@@ -386,6 +423,7 @@ def opc_connect_and_write_value(
386423
value: Union[bool, float, int, str],
387424
timeout: int,
388425
node_lookup_mode: Literal["hierarchical", "direct"] = "hierarchical",
426+
value_type: str = "String",
389427
) -> Tuple[bool, str]:
390428
logging.debug(
391429
f"OPC Writer attempting to connect and write value={value} to {url}/{object_name}/{variable_name}"
@@ -401,6 +439,7 @@ def opc_connect_and_write_value(
401439
value=value,
402440
timeout=timeout,
403441
node_lookup_mode=node_lookup_mode,
442+
value_type=value_type,
404443
)
405444
logging.debug(
406445
f"OPC Writer successfully wrote value to {url}/{object_name}/{variable_name}"
@@ -424,6 +463,7 @@ def _opc_connect_and_write_value(
424463
value: Union[bool, float, int, str],
425464
timeout: int,
426465
node_lookup_mode: Literal["hierarchical", "direct"] = "hierarchical",
466+
value_type: str = "String",
427467
):
428468
logging.debug(f"OPC Writer creating client for {url} with timeout={timeout}")
429469
client = Client(url=url, sync_wrapper_timeout=timeout)
@@ -514,15 +554,50 @@ def _opc_connect_and_write_value(
514554
raise Exception(f"UNHANDLED ERROR: {type(exc)} {exc}")
515555

516556
try:
517-
logging.debug(f"OPC Writer writing value '{value}' to variable")
518-
var.write_value(value)
557+
logging.debug(
558+
f"OPC Writer writing value '{value}' to variable with type '{value_type}'"
559+
)
560+
# Use proper OPC UA type specification using VariantType enum
561+
if value_type in [BOOLEAN_KIND, "Boolean"]:
562+
var.set_value(value, VariantType.Boolean)
563+
elif value_type == "Double":
564+
var.set_value(value, VariantType.Double)
565+
elif value_type in [FLOAT_KIND, "Float"]:
566+
var.set_value(value, VariantType.Float)
567+
elif value_type == "Int16":
568+
var.set_value(value, VariantType.Int16)
569+
elif value_type == "Int32":
570+
var.set_value(value, VariantType.Int32)
571+
elif value_type in ["Int64", INTEGER_KIND, "Integer"]:
572+
var.set_value(value, VariantType.Int64)
573+
elif value_type == "SByte":
574+
var.set_value(value, VariantType.SByte)
575+
elif value_type in [STRING_KIND, "String"]:
576+
var.set_value(value, VariantType.String)
577+
elif value_type == "UInt16":
578+
var.set_value(value, VariantType.UInt16)
579+
elif value_type == "UInt32":
580+
var.set_value(value, VariantType.UInt32)
581+
elif value_type == "UInt64":
582+
var.set_value(value, VariantType.UInt64)
583+
else:
584+
logging.error(f"OPC Writer unsupported value type: {value_type}")
585+
safe_disconnect(client)
586+
raise UnsupportedTypeError(f"Value type '{value_type}' is not supported.")
519587
logging.info(
520588
f"OPC Writer successfully wrote '{value}' to variable at {object_name}/{variable_name}"
521589
)
590+
except UnsupportedTypeError:
591+
raise
522592
except BadTypeMismatch as exc:
523-
logging.error(f"OPC Writer type mismatch error: {exc}")
593+
node_type = get_node_data_type(var)
594+
logging.error(
595+
f"OPC Writer type mismatch: tried to write value '{value}' (type: {type(value).__name__}) to node with data type {node_type}. Error: {exc}"
596+
)
524597
safe_disconnect(client)
525-
raise Exception(f"WRONG TYPE ERROR: {exc}")
598+
raise Exception(
599+
f"WRONG TYPE ERROR: Tried to write value '{value}' (type: {type(value).__name__}) but node expects type {node_type}. {exc}"
600+
)
526601
except Exception as exc:
527602
logging.error(f"OPC Writer unhandled write error: {type(exc)} {exc}")
528603
safe_disconnect(client)

0 commit comments

Comments
 (0)