diff --git a/src/ophyd_async/epics/core/_aioca.py b/src/ophyd_async/epics/core/_aioca.py index bca8892371..ff2fff15b5 100644 --- a/src/ophyd_async/epics/core/_aioca.py +++ b/src/ophyd_async/epics/core/_aioca.py @@ -66,7 +66,11 @@ def _metadata_from_augmented_value( metadata = metadata.copy() if hasattr(value, "units") and datatype not in (str, bool): metadata["units"] = value.units - if hasattr(value, "precision") and not isnan(value.precision): + if ( + hasattr(value, "precision") + and not isnan(value.precision) + and datatype is not int + ): metadata["precision"] = value.precision if (limits := _limits_from_augmented_value(value)) and datatype is not bool: metadata["limits"] = limits @@ -102,6 +106,11 @@ def __getattribute__(self, __name: str) -> Any: raise NotImplementedError("No PV has been set as connect() has not been called") +class CaIntConverter(CaConverter[int]): + def value(self, value: AugmentedValue) -> int: + return int(value) # type: ignore + + class CaArrayConverter(CaConverter[np.ndarray]): def value(self, value: AugmentedValue) -> np.ndarray: # A less expensive conversion @@ -204,7 +213,7 @@ def make_converter( and get_unique({k: v.precision for k, v in values.items()}, "precision") == 0 ): # Allow int signals to represent float records when prec is 0 - return CaConverter(int, pv_dbr) + return CaIntConverter(int, pv_dbr) elif datatype in (None, inferred_datatype): # If datatype matches what we are given then allow it and use inferred converter return converter_cls(inferred_datatype, pv_dbr) diff --git a/src/ophyd_async/epics/core/_p4p.py b/src/ophyd_async/epics/core/_p4p.py index e0e2713b0f..1f5c3acbe6 100644 --- a/src/ophyd_async/epics/core/_p4p.py +++ b/src/ophyd_async/epics/core/_p4p.py @@ -72,6 +72,7 @@ def _metadata_from_value(datatype: type[SignalDatatype], value: Any) -> SignalMe hasattr(display_data, "precision") and not isnan(display_data.precision) and specifier[-1] in _float_specifiers + and datatype is not int ): metadata["precision"] = display_data.precision if (limits := _limits_from_value(value)) and specifier[-1] in _number_specifiers: @@ -93,9 +94,7 @@ def __init__(self, datatype: type[SignalDatatypeT]): self.datatype = datatype def value(self, value: Any) -> SignalDatatypeT: - # for channel access ca_xxx classes, this - # invokes __pos__ operator to return an instance of - # the builtin base class + # Normally the value will be of the correct python type return value["value"] def write_value(self, value: Any) -> Any: @@ -103,6 +102,15 @@ def write_value(self, value: Any) -> Any: return value +class PvaIntConverter(PvaConverter[int]): + def __init__(self): + super().__init__(int) + + def value(self, value: Any) -> int: + # Convert to an int + return int(value["value"]) + + class PvaLongStringConverter(PvaConverter[str]): def __init__(self): super().__init__(str) @@ -270,7 +278,7 @@ def make_converter(datatype: type | None, values: dict[str, Any]) -> PvaConverte == 0 ): # Allow int signals to represent float records when prec is 0 - return PvaConverter(int) + return PvaIntConverter() elif inferred_datatype is str and (enum_cls := get_enum_cls(datatype)): # Allow strings to be used as enums until QSRV supports this return PvaConverter(str) diff --git a/src/ophyd_async/epics/testing/_example_ioc.py b/src/ophyd_async/epics/testing/_example_ioc.py index 6ae7a313fa..f4a2de7947 100644 --- a/src/ophyd_async/epics/testing/_example_ioc.py +++ b/src/ophyd_async/epics/testing/_example_ioc.py @@ -36,6 +36,7 @@ class EpicsTestTable(Table): class EpicsTestCaDevice(EpicsDevice): my_int: A[SignalRW[int], PvSuffix("int")] my_float: A[SignalRW[float], PvSuffix("float")] + float_prec_0: A[SignalRW[int], PvSuffix("float_prec_0")] my_str: A[SignalRW[str], PvSuffix("str")] longstr: A[SignalRW[str], PvSuffix("longstr")] longstr2: A[SignalRW[str], PvSuffix("longstr2.VAL$")] diff --git a/src/ophyd_async/testing/_assert.py b/src/ophyd_async/testing/_assert.py index da2281c1d8..48d8a35538 100644 --- a/src/ophyd_async/testing/_assert.py +++ b/src/ophyd_async/testing/_assert.py @@ -182,6 +182,7 @@ def __init__(self, signal: SignalR): async def assert_updates(self, expected_value): # Get an update, value and reading + expected_type = type(expected_value) if isinstance(expected_value, Table): expected_value = ApproxTable(expected_value) else: @@ -191,6 +192,7 @@ async def assert_updates(self, expected_value): reading = await self.signal.read() # Check they match what we expected assert value == expected_value + assert type(value) is expected_type expected_reading = { self.signal.name: { "value": expected_value, diff --git a/tests/epics/signal/test_signals.py b/tests/epics/signal/test_signals.py index 7947b88ed3..e7fa6c3cbf 100644 --- a/tests/epics/signal/test_signals.py +++ b/tests/epics/signal/test_signals.py @@ -152,37 +152,81 @@ async def assert_monitor_then_put( ), "my_float": ExpectedData(3.141, 43.5, "number", "