Skip to content

Commit 1fd8830

Browse files
committed
Allow binding sub controller to Controller via __setattr__
1 parent 66b7f5c commit 1fd8830

File tree

13 files changed

+79
-80
lines changed

13 files changed

+79
-80
lines changed

docs/snippets/dynamic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ async def initialise(self):
126126
idx + 1, ramp_parameters, self._io
127127
)
128128
await ramp_controller.initialise()
129-
self.register_sub_controller(f"Ramp{idx + 1:02d}", ramp_controller)
129+
self.add_sub_controller(f"Ramp{idx + 1:02d}", ramp_controller)
130130

131131
await self._connection.close()
132132

docs/snippets/static10.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def __init__(self, ramp_count: int, settings: IPConnectionSettings):
7171
for index in range(1, ramp_count + 1):
7272
controller = TemperatureRampController(index, self._connection)
7373
self._ramp_controllers.append(controller)
74-
self.register_sub_controller(f"R{index}", controller)
74+
self.add_sub_controller(f"R{index}", controller)
7575

7676
async def connect(self):
7777
await self._connection.connect(self._ip_settings)

docs/snippets/static11.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(self, ramp_count: int, settings: IPConnectionSettings):
7878
for index in range(1, ramp_count + 1):
7979
controller = TemperatureRampController(index, self._connection)
8080
self._ramp_controllers.append(controller)
81-
self.register_sub_controller(f"R{index}", controller)
81+
self.add_sub_controller(f"R{index}", controller)
8282

8383
async def connect(self):
8484
await self._connection.connect(self._ip_settings)

docs/snippets/static12.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def __init__(self, ramp_count: int, settings: IPConnectionSettings):
8383
for index in range(1, ramp_count + 1):
8484
controller = TemperatureRampController(index, self._connection)
8585
self._ramp_controllers.append(controller)
86-
self.register_sub_controller(f"R{index}", controller)
86+
self.add_sub_controller(f"R{index}", controller)
8787

8888
async def connect(self):
8989
await self._connection.connect(self._ip_settings)

docs/snippets/static13.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def __init__(self, ramp_count: int, settings: IPConnectionSettings):
8484
for index in range(1, ramp_count + 1):
8585
controller = TemperatureRampController(index, self._connection)
8686
self._ramp_controllers.append(controller)
87-
self.register_sub_controller(f"R{index}", controller)
87+
self.add_sub_controller(f"R{index}", controller)
8888

8989
async def connect(self):
9090
await self._connection.connect(self._ip_settings)

src/fastcs/controller.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,22 +169,23 @@ def add_attribute(self, name, attribute: Attribute):
169169
self.attributes[name] = attribute
170170
super().__setattr__(name, attribute)
171171

172-
def register_sub_controller(self, name: str, sub_controller: Controller):
172+
def add_sub_controller(self, name: str, sub_controller: Controller):
173173
if name in self.__sub_controller_tree.keys():
174174
raise ValueError(
175-
f"Controller {self} already has a sub controller registered as {name}"
175+
f"Cannot add sub controller {name}. "
176+
f"Controller {self} has existing sub controller {name}"
177+
)
178+
elif name in self.attributes:
179+
raise ValueError(
180+
f"Cannot add sub controller {name}. "
181+
f"Controller {self} has existing attribute {name}"
176182
)
177183

178-
self.__sub_controller_tree[name] = sub_controller
179184
sub_controller.set_path(self.path + [name])
185+
self.__sub_controller_tree[name] = sub_controller
186+
super().__setattr__(name, sub_controller)
180187

181188
if isinstance(sub_controller.root_attribute, Attribute):
182-
if name in self.attributes:
183-
raise TypeError(
184-
f"Cannot set sub controller `{name}` root attribute "
185-
f"on the parent controller `{type(self).__name__}` "
186-
f"as it already has an attribute of that name."
187-
)
188189
self.attributes[name] = sub_controller.root_attribute
189190

190191
def get_sub_controllers(self) -> dict[str, Controller]:
@@ -198,6 +199,8 @@ def __repr__(self):
198199
def __setattr__(self, name, value):
199200
if isinstance(value, Attribute):
200201
self.add_attribute(name, value)
202+
elif isinstance(value, Controller):
203+
self.add_sub_controller(name, value)
201204
else:
202205
super().__setattr__(name, value)
203206

src/fastcs/demo/controllers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def __init__(self, settings: TemperatureControllerSettings) -> None:
8282
for index in range(1, settings.num_ramp_controllers + 1):
8383
controller = TemperatureRampController(index, self.connection)
8484
self._ramp_controllers.append(controller)
85-
self.register_sub_controller(f"R{index}", controller)
85+
self.add_sub_controller(f"R{index}", controller)
8686

8787
@command()
8888
async def cancel_all(self) -> None:

src/fastcs/transport/epics/ca/ioc.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def _add_sub_controller_pvi_info(pv_prefix: str, parent: ControllerAPI):
114114
parent_pvi = ":".join([pv_prefix] + parent.path + ["PVI"])
115115

116116
for child in parent.sub_apis.values():
117-
child_pvi = ":".join([pv_prefix] + child.path + ["PVI"])
117+
child_pvi = ":".join([pv_prefix] + _controller_pv_prefix(child.path) + ["PVI"])
118118
child_name = child.path[-1].lower()
119119

120120
_add_pvi_info(child_pvi, parent_pvi, child_name)
@@ -123,14 +123,14 @@ def _add_sub_controller_pvi_info(pv_prefix: str, parent: ControllerAPI):
123123

124124

125125
def _create_and_link_attribute_pvs(
126-
pv_prefix: str, root_controller_api: ControllerAPI
126+
root_pv_prefix: str, root_controller_api: ControllerAPI
127127
) -> None:
128128
for controller_api in root_controller_api.walk_api():
129129
path = controller_api.path
130130
for attr_name, attribute in controller_api.attributes.items():
131131
pv_name = snake_to_pascal(attr_name)
132-
_pv_prefix = ":".join([pv_prefix] + path)
133-
full_pv_name_length = len(f"{_pv_prefix}:{pv_name}")
132+
pv_prefix = ":".join([root_pv_prefix] + _controller_pv_prefix(path))
133+
full_pv_name_length = len(f"{pv_prefix}:{pv_name}")
134134

135135
if full_pv_name_length > EPICS_MAX_NAME_LENGTH:
136136
attribute.enabled = False
@@ -152,15 +152,15 @@ def _create_and_link_attribute_pvs(
152152
attribute.enabled = False
153153
else:
154154
_create_and_link_read_pv(
155-
_pv_prefix, f"{pv_name}_RBV", attr_name, attribute
155+
pv_prefix, f"{pv_name}_RBV", attr_name, attribute
156156
)
157157
_create_and_link_write_pv(
158-
_pv_prefix, pv_name, attr_name, attribute
158+
pv_prefix, pv_name, attr_name, attribute
159159
)
160160
case AttrR():
161-
_create_and_link_read_pv(_pv_prefix, pv_name, attr_name, attribute)
161+
_create_and_link_read_pv(pv_prefix, pv_name, attr_name, attribute)
162162
case AttrW():
163-
_create_and_link_write_pv(_pv_prefix, pv_name, attr_name, attribute)
163+
_create_and_link_write_pv(pv_prefix, pv_name, attr_name, attribute)
164164

165165

166166
def _create_and_link_read_pv(
@@ -234,22 +234,22 @@ async def async_write_display(value: T):
234234

235235

236236
def _create_and_link_command_pvs(
237-
pv_prefix: str, root_controller_api: ControllerAPI
237+
root_pv_prefix: str, root_controller_api: ControllerAPI
238238
) -> None:
239239
for controller_api in root_controller_api.walk_api():
240240
path = controller_api.path
241241
for attr_name, method in controller_api.command_methods.items():
242242
pv_name = snake_to_pascal(attr_name)
243-
_pv_prefix = ":".join([pv_prefix] + path)
244-
if len(f"{_pv_prefix}:{pv_name}") > EPICS_MAX_NAME_LENGTH:
243+
pv_prefix = ":".join([root_pv_prefix] + _controller_pv_prefix(path))
244+
if len(f"{pv_prefix}:{pv_name}") > EPICS_MAX_NAME_LENGTH:
245245
print(
246246
f"Not creating PV for {attr_name} as full name would exceed"
247247
f" {EPICS_MAX_NAME_LENGTH} characters"
248248
)
249249
method.enabled = False
250250
else:
251251
_create_and_link_command_pv(
252-
_pv_prefix,
252+
pv_prefix,
253253
pv_name,
254254
attr_name,
255255
method,
@@ -302,3 +302,7 @@ def _add_attr_pvi_info(
302302
}
303303
},
304304
)
305+
306+
307+
def _controller_pv_prefix(controller_path: list[str]) -> list[str]:
308+
return [snake_to_pascal(node) for node in controller_path]

tests/assertable_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(self) -> None:
4646
for index in range(1, 3):
4747
controller = TestSubController()
4848
self._sub_controllers.append(controller)
49-
self.register_sub_controller(f"SubController{index:02d}", controller)
49+
self.add_sub_controller(f"SubController{index:02d}", controller)
5050

5151
initialised = False
5252
connected = False

tests/example_p4p_ioc.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,9 @@ async def i(self):
7070

7171
def run(pv_prefix="P4P_TEST_DEVICE"):
7272
controller = ParentController()
73-
controller.register_sub_controller(
74-
"Child1", ChildController(description="some sub controller")
75-
)
76-
controller.register_sub_controller(
77-
"Child2", ChildController(description="another sub controller")
78-
)
73+
controller.child1 = ChildController(description="some sub controller")
74+
controller.child2 = ChildController(description="another sub controller")
75+
7976
fastcs = FastCS(
8077
controller, [EpicsPVATransport(pva_ioc=EpicsIOCOptions(pv_prefix=pv_prefix))]
8178
)

0 commit comments

Comments
 (0)