11from collections .abc import Callable
2- from dataclasses import dataclass
2+ from dataclasses import asdict , dataclass
33from types import MethodType
44from typing import Any , Literal
55
6- from softioc import builder , softioc
6+ import numpy as np
7+ from softioc import builder , fields , softioc
78from softioc .asyncio_dispatcher import AsyncioDispatcher
9+ from softioc .imports import db_put_field
810from softioc .pythonSoftIoc import RecordWrapper
911
1012from fastcs .attributes import AttrR , AttrRW , AttrW
1719 enum_value_to_index ,
1820)
1921from fastcs .controller import BaseController
20- from fastcs .datatypes import Bool , Float , Int , String , T
22+ from fastcs .datatypes import Bool , DataType , Float , Int , String , T
2123from fastcs .exceptions import FastCSException
2224from fastcs .mapping import Mapping
2325
@@ -30,6 +32,35 @@ class EpicsIOCOptions:
3032 name_options : EpicsNameOptions = EpicsNameOptions ()
3133
3234
35+ DATATYPE_NAME_TO_RECORD_FIELD = {
36+ "prec" : "PREC" ,
37+ "units" : "EGU" ,
38+ "min" : "DRVL" ,
39+ "max" : "DRVH" ,
40+ "min_alarm" : "LOPR" ,
41+ "max_alarm" : "HOPR" ,
42+ "znam" : "ZNAM" ,
43+ "onam" : "ONAM" ,
44+ }
45+
46+
47+ def datatype_to_epics_fields (datatype : DataType ) -> dict [str , Any ]:
48+ return {
49+ DATATYPE_NAME_TO_RECORD_FIELD [field ]: value
50+ for field , value in asdict (datatype ).items ()
51+ }
52+
53+
54+ def reload_attribute_fields (pv : str , DataType : DataType ):
55+ """If the ioc side changes a field on the attribute
56+ e.g ``units`` then this method will update it on the attribute"""
57+
58+ for name , value in datatype_to_epics_fields (DataType ):
59+ # TODO: can we just make every dtype a string and have the ioc convert?
60+ array = np .require (value , dtype = np .dtype ("S40" ))
61+ db_put_field (f"{ pv } .{ name } " , fields .DBR_STRING , array , 1 )
62+
63+
3364class EpicsIOC :
3465 def __init__ (
3566 self , pv_prefix : str , mapping : Mapping , options : EpicsIOCOptions | None = None
@@ -327,32 +358,32 @@ def _get_input_record(pv: str, attribute: AttrR) -> RecordWrapper:
327358 state_keys = dict (zip (MBB_STATE_FIELDS , attribute .allowed_values , strict = False ))
328359 return builder .mbbIn (pv , ** state_keys , ** attribute_fields )
329360
361+ def datatype_updater (datatype : DataType ):
362+ reload_attribute_fields (pv , datatype )
363+
364+ attribute .set_update_datatype_callback (datatype_updater )
365+
330366 match attribute .datatype :
331- case Bool (znam , onam ):
332- return builder .boolIn (pv , ZNAM = znam , ONAM = onam , ** attribute_fields )
333- case Int (units , min , max , min_alarm , max_alarm ):
367+ case Bool ():
368+ return builder .boolIn (
369+ pv , ** datatype_to_epics_fields (attribute .datatype ), ** attribute_fields
370+ )
371+ case Int ():
334372 return builder .longIn (
335373 pv ,
336- EGU = units ,
337- DRVL = min ,
338- DRVH = max ,
339- LOPR = min_alarm ,
340- HOPR = max_alarm ,
374+ ** datatype_to_epics_fields (attribute .datatype ),
341375 ** attribute_fields ,
342376 )
343- case Float (prec , units , min , max , min_alarm , max_alarm ):
377+ case Float ():
344378 return builder .aIn (
345379 pv ,
346- PREC = prec ,
347- EGU = units ,
348- DRVL = min ,
349- DRVH = max ,
350- LOPR = min_alarm ,
351- HOPR = max_alarm ,
380+ ** datatype_to_epics_fields (attribute .datatype ),
352381 ** attribute_fields ,
353382 )
354383 case String ():
355- return builder .longStringIn (pv , ** attribute_fields )
384+ return builder .longStringIn (
385+ pv , ** datatype_to_epics_fields (attribute .datatype ), ** attribute_fields
386+ )
356387 case _:
357388 raise FastCSException (
358389 f"Unsupported type { type (attribute .datatype )} : { attribute .datatype } "
@@ -376,38 +407,33 @@ def _get_output_record(pv: str, attribute: AttrW, on_update: Callable) -> Any:
376407 ** attribute_fields ,
377408 )
378409
410+ def datatype_updater (datatype : DataType ):
411+ reload_attribute_fields (pv , datatype )
412+
413+ attribute .set_update_datatype_callback (datatype_updater )
414+
379415 match attribute .datatype :
380- case Bool (znam , onam ):
416+ case Bool ():
381417 return builder .boolOut (
382418 pv ,
383- ZNAM = znam ,
384- ONAM = onam ,
419+ ** datatype_to_epics_fields (attribute .datatype ),
385420 always_update = True ,
386421 on_update = on_update ,
387422 )
388- case Int (units , min , max , min_alarm , max_alarm ):
423+ case Int ():
389424 return builder .longOut (
390425 pv ,
391426 always_update = True ,
392427 on_update = on_update ,
393- EGU = units ,
394- DRVL = min ,
395- DRVH = max ,
396- LOPR = min_alarm ,
397- HOPR = max_alarm ,
428+ ** datatype_to_epics_fields (attribute .datatype ),
398429 ** attribute_fields ,
399430 )
400- case Float (prec , units , min , max , min_alarm , max_alarm ):
431+ case Float ():
401432 return builder .aOut (
402433 pv ,
403434 always_update = True ,
404435 on_update = on_update ,
405- PREC = prec ,
406- EGU = units ,
407- DRVL = min ,
408- DRVH = max ,
409- LOPR = min_alarm ,
410- HOPR = max_alarm ,
436+ ** datatype_to_epics_fields (attribute .datatype ),
411437 ** attribute_fields ,
412438 )
413439 case String ():
0 commit comments