Skip to content

Commit 3d27c8c

Browse files
committed
custom tango
1 parent f8ec6c8 commit 3d27c8c

File tree

8 files changed

+1166
-0
lines changed

8 files changed

+1166
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .magnet_device import MagnetDevice
2+
from .power_converter_device import PowerConverterDevice
3+
4+
__all__ = [
5+
'MagnetDevice',
6+
'PowerConverterDevice',
7+
]
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
from tango import DeviceProxy, DevState, AttrWriteType, AttrDataFormat, DevString, DevFailed
2+
from tango.server import Device, attribute, command, device_property
3+
import numpy as np
4+
import asyncio
5+
import threading
6+
from dt4acc.core.utils.logger import get_logger
7+
from dt4acc.custom_epics.data.querries import get_unique_power_converters, get_magnets_per_power_converters
8+
from dt4acc.custom_epics.ioc.handlers import update_manager, handle_device_update
9+
from dt4acc.custom_epics.ioc.liasion_translation_manager import build_managers, element_method
10+
from bact_twin_architecture.data_model.identifiers import LatticeElementPropertyID, DevicePropertyID
11+
from bact_twin_architecture.bl.bessyii_yellow_pages import bessyii_yellow_pages
12+
13+
logger = get_logger()
14+
15+
class MagnetDevice(Device):
16+
"""
17+
Tango device class for controlling magnets in the accelerator.
18+
19+
"""
20+
21+
# Device properties
22+
k_value = device_property(
23+
dtype=float,
24+
default_value=0.0,
25+
doc="Magnet strength factor (k-value)"
26+
)
27+
28+
name = device_property(
29+
dtype=str,
30+
default_value="",
31+
doc="Magnet name"
32+
)
33+
34+
type = device_property(
35+
dtype=str,
36+
default_value="unknown",
37+
doc="Magnet type"
38+
)
39+
40+
def init_device(self):
41+
"""Initialize the device."""
42+
try:
43+
print("\n[DEBUG] Initializing MagnetDevice:")
44+
print(" - Getting device properties")
45+
dev_prop = self.get_device_properties()
46+
print(f" - Device properties: {dev_prop}")
47+
48+
# Get device name from properties
49+
if not hasattr(dev_prop, 'name') or not dev_prop.name:
50+
print(" - ERROR: Device name property not set")
51+
# Try to get name from device name
52+
device_name = self.get_name()
53+
print(f" - Device name from get_name(): {device_name}")
54+
if device_name:
55+
# Extract name from device name (format: tango_server/test/MagnetDevice_HS1MD4R)
56+
try:
57+
self.name = device_name.split('_')[-1]
58+
print(f" - Extracted name from device name: {self.name}")
59+
except:
60+
print(" - Could not extract name from device name")
61+
raise DevFailed("Device name property not set")
62+
else:
63+
raise DevFailed("Device name property not set")
64+
65+
print(f" - Device name: {self.name}")
66+
67+
# Initialize attributes
68+
try:
69+
print(" - Initializing attributes")
70+
self._init_attributes()
71+
print(" - Attributes initialized successfully")
72+
except Exception as e:
73+
print(f" - ERROR: Failed to initialize attributes: {str(e)}")
74+
raise
75+
76+
# Initialize event loop
77+
try:
78+
self._loop = asyncio.new_event_loop()
79+
asyncio.set_event_loop(self._loop)
80+
# Start event loop in a separate thread
81+
self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
82+
self._loop_thread.start()
83+
print(" - Event loop initialized successfully")
84+
except Exception as e:
85+
print(f" - ERROR: Failed to initialize event loop: {str(e)}")
86+
raise
87+
88+
print(" - Device initialization completed successfully")
89+
90+
except Exception as e:
91+
print(f"\n[ERROR] Device initialization failed:")
92+
print(f" - Error: {str(e)}")
93+
raise
94+
95+
def _run_event_loop(self):
96+
"""Run the event loop in a separate thread."""
97+
asyncio.set_event_loop(self._loop)
98+
self._loop.run_forever()
99+
100+
def _init_attributes(self):
101+
"""Initialize device attributes with default values."""
102+
try:
103+
print(" - Initializing device attributes")
104+
105+
# Get initial values from update manager
106+
try:
107+
val = update_manager.peek_engine(
108+
LatticeElementPropertyID(element_name=self.name, property="main_strength")
109+
)
110+
print(f" * Initial magnetic strength value: {val}")
111+
except Exception as e:
112+
logger.warning(f"Could not get initial values from update manager: {str(e)}")
113+
val = 0.0
114+
115+
# Get associated power converter from EPICS query
116+
try:
117+
print(f" * Looking up power converter for magnet {self.name}")
118+
power_converters = get_unique_power_converters()
119+
print(f" * Found {len(power_converters)} power converters")
120+
121+
# Debug print all power converters and their magnets
122+
for pc in power_converters:
123+
magnets = get_magnets_per_power_converters(pc)
124+
print(f" * Power converter {pc} has magnets: {[m['name'] for m in magnets]}")
125+
126+
# Find power converter for this magnet
127+
self._power_converter = None
128+
for pc in power_converters:
129+
magnets = get_magnets_per_power_converters(pc)
130+
if any(m['name'] == self.name for m in magnets):
131+
self._power_converter = pc
132+
print(f" * Found power converter {pc} for magnet {self.name}")
133+
break
134+
135+
if not self._power_converter:
136+
print(f" * WARNING: No power converter found for magnet {self.name}")
137+
logger.warning(f"No power converter found for magnet {self.name}")
138+
else:
139+
logger.info(f"Found associated power converter: {self._power_converter}")
140+
except Exception as e:
141+
print(f" * ERROR: Failed to get power converter: {str(e)}")
142+
logger.warning(f"Could not get power converter from EPICS query: {str(e)}")
143+
self._power_converter = None
144+
145+
# Initialize attributes with default values
146+
self._magnetic_strength = self.k_value or 0.0 # as like Cm:set in EPICS
147+
self._magnetic_strength_readback = val # like Cm:rdbk in EPICS
148+
self._current = 0.0 # im:I in EPICS
149+
self._power_supply_current = 0.0
150+
self._x_position = 0.0 # x:set like in EPICS
151+
self._y_position = 0.0 # y:set like in EPICS
152+
self._cm_set = val
153+
154+
print(" * All attributes initialized successfully")
155+
156+
except Exception as e:
157+
print(f" * ERROR: Failed to initialize attributes: {str(e)}")
158+
raise
159+
160+
def _run_async_update(self, update_func):
161+
"""Helper method to run async updates in the event loop."""
162+
try:
163+
future = asyncio.run_coroutine_threadsafe(update_func, self._loop)
164+
future.result(timeout=10.0) # Increased timeout to 10 seconds
165+
except asyncio.TimeoutError:
166+
logger.error("Async update timed out after 10 seconds")
167+
raise DevFailed("Update operation timed out")
168+
except Exception as e:
169+
logger.error(f"Error in async update: {str(e)}")
170+
raise
171+
172+
# Magnetic field control attributes
173+
@attribute(dtype=float, access=AttrWriteType.READ_WRITE)
174+
def magnetic_strength(self):
175+
"""Get magnetic strength (Cm:set in EPICS)."""
176+
return self._magnetic_strength
177+
178+
@magnetic_strength.write
179+
def magnetic_strength(self, value):
180+
"""Set magnetic strength (Cm:set in EPICS)."""
181+
try:
182+
self._magnetic_strength = float(value)
183+
184+
# Get associated power converter
185+
if not hasattr(self, '_power_converter') or self._power_converter is None:
186+
print(f"Looking up power converter for magnet {self.name}")
187+
power_converters = get_unique_power_converters()
188+
print(f"Found {len(power_converters)} power converters")
189+
190+
# Find power converter for this magnet
191+
self._power_converter = None
192+
for pc in power_converters:
193+
magnets = get_magnets_per_power_converters(pc)
194+
if any(m['name'] == self.name for m in magnets):
195+
self._power_converter = pc
196+
print(f"Found power converter {pc} for magnet {self.name}")
197+
break
198+
199+
if not self._power_converter:
200+
raise DevFailed(f"No power converter found for magnet {self.name}")
201+
202+
print(f"Using power converter {self._power_converter} for magnet {self.name}")
203+
print(f"Current magnetic strength: {self._magnetic_strength}")
204+
print(f"Previous magnetic strength: {self._magnetic_strength_readback}")
205+
206+
try:
207+
# Update only the power converter's set_current
208+
# The LiaisonManager will handle mapping this to the appropriate lattice property
209+
print(f"Updating power converter {self._power_converter} set_current to {value}")
210+
self._run_async_update(handle_device_update(self._power_converter, "set_current", value))
211+
212+
# Update readback to match EPICS behavior
213+
self._magnetic_strength_readback = value
214+
logger.info(f"Updated magnetic strength to {value} using power converter {self._power_converter}")
215+
216+
except Exception as e:
217+
if "array must not contain infs or NaNs" in str(e):
218+
logger.warning("Twiss calculation error - continuing with update")
219+
logger.warning(f"Error occurred with magnetic strength value: {value}")
220+
# Still update the readback since the value was set
221+
self._magnetic_strength_readback = value
222+
else:
223+
raise
224+
225+
except Exception as e:
226+
logger.error(f"Error updating magnetic strength: {str(e)}")
227+
raise
228+
229+
@attribute(dtype=float)
230+
def magnetic_strength_readback(self):
231+
"""Get magnetic strength readback (Cm:rdbk in EPICS)."""
232+
return self._magnetic_strength_readback
233+
234+
@attribute(dtype=float, access=AttrWriteType.READ_WRITE)
235+
def current(self):
236+
"""Get current (im:I in EPICS)."""
237+
return self._current
238+
239+
@current.write
240+
def current(self, value):
241+
"""Set current (im:I in EPICS)."""
242+
try:
243+
self._current = float(value)
244+
# Use EPICS update handler with set_current property
245+
if self._power_converter:
246+
self._run_async_update(handle_device_update(self._power_converter, "set_current", value))
247+
logger.info(f"Updated current to {value}")
248+
except Exception as e:
249+
logger.error(f"Error updating current: {str(e)}")
250+
raise
251+
252+
@attribute(dtype=float)
253+
def power_supply_current(self):
254+
"""Get power supply current."""
255+
return self._power_supply_current
256+
257+
@attribute(dtype=float, access=AttrWriteType.READ_WRITE)
258+
def x_position(self):
259+
"""Get x position (x:set in EPICS)."""
260+
return self._x_position
261+
262+
@x_position.write
263+
def x_position(self, value):
264+
"""Set x position (x:set in EPICS)."""
265+
try:
266+
self._x_position = float(value)
267+
# Use EPICS update handler with x_kick property
268+
self._run_async_update(handle_device_update(self.name, "x_kick", value))
269+
logger.info(f"Updated x position to {value}")
270+
except Exception as e:
271+
logger.error(f"Error updating x position: {str(e)}")
272+
raise
273+
274+
@attribute(dtype=float, access=AttrWriteType.READ_WRITE)
275+
def y_position(self):
276+
"""Get y position (y:set in EPICS)."""
277+
return self._y_position
278+
279+
@y_position.write
280+
def y_position(self, value):
281+
"""Set y position (y:set in EPICS)."""
282+
try:
283+
self._y_position = float(value)
284+
# Use EPICS update handler with y_kick property
285+
self._run_async_update(handle_device_update(self.name, "y_kick", value))
286+
logger.info(f"Updated y position to {value}")
287+
except Exception as e:
288+
logger.error(f"Error updating y position: {str(e)}")
289+
raise
290+
291+
@attribute(dtype=float)
292+
def cm_set(self):
293+
"""Get Cm set value."""
294+
return self._cm_set
295+
296+
# Commands
297+
@command(dtype_in=None, doc_in="Reset the magnet to its default state")
298+
def reset(self):
299+
"""Reset the magnet to its default state."""
300+
try:
301+
self._magnetic_strength = self.k_value or 0.0
302+
self._magnetic_strength_readback = self.k_value or 0.0
303+
self._current = 0.0
304+
self._power_supply_current = 0.0
305+
self._x_position = 0.0
306+
self._y_position = 0.0
307+
self.set_state(DevState.OFF)
308+
logger.info(f"Magnet {self.name}: Reset to default state")
309+
except Exception as e:
310+
logger.error(f"Error resetting magnet: {str(e)}")
311+
raise
312+
313+
@command(dtype_in=None, doc_in="Calibrate the magnet")
314+
def calibrate(self):
315+
"""Calibrate the magnet."""
316+
try:
317+
# Here you would implement the actual calibration logic
318+
logger.info(f"Magnet {self.name}: Calibration started")
319+
# Simulate calibration
320+
self._magnetic_strength_readback = self._magnetic_strength
321+
logger.info(f"Magnet {self.name}: Calibration completed")
322+
except Exception as e:
323+
logger.error(f"Error calibrating magnet: {str(e)}")
324+
raise

0 commit comments

Comments
 (0)