diff --git a/docs/snippets/static05.py b/docs/snippets/static05.py index 9e0bca34e..30ddac1d5 100644 --- a/docs/snippets/static05.py +++ b/docs/snippets/static05.py @@ -1,27 +1,26 @@ +from pathlib import Path + from fastcs.attributes import AttrR -from fastcs.connections import IPConnection, IPConnectionSettings from fastcs.controller import Controller from fastcs.datatypes import String from fastcs.launch import FastCS -from fastcs.transport.epics.ca.options import EpicsCAOptions +from fastcs.transport.epics.ca.options import EpicsCAOptions, EpicsGUIOptions from fastcs.transport.epics.options import EpicsIOCOptions class TemperatureController(Controller): device_id = AttrR(String()) - def __init__(self, settings: IPConnectionSettings): - super().__init__() - - self._ip_settings = settings - self.connection = IPConnection() - - async def connect(self): - await self.connection.connect(self._ip_settings) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) +fastcs = FastCS(TemperatureController(), [epics_options]) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) -connection_settings = IPConnectionSettings("localhost", 25565) -fastcs = FastCS(TemperatureController(connection_settings), [epics_options]) +fastcs.create_gui() # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static06.py b/docs/snippets/static06.py index 31f21cc9b..c6be250c0 100644 --- a/docs/snippets/static06.py +++ b/docs/snippets/static06.py @@ -1,41 +1,16 @@ -from __future__ import annotations +from pathlib import Path -from dataclasses import dataclass - -from fastcs.attributes import AttrHandlerR, AttrR +from fastcs.attributes import AttrR from fastcs.connections import IPConnection, IPConnectionSettings -from fastcs.controller import BaseController, Controller +from fastcs.controller import Controller from fastcs.datatypes import String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions - - -@dataclass -class IDUpdater(AttrHandlerR): - update_period: float | None = 0.2 - _controller: TemperatureController | None = None - - async def initialise(self, controller: BaseController): - assert isinstance(controller, TemperatureController) - self._controller = controller - - @property - def controller(self) -> TemperatureController: - if self._controller is None: - raise RuntimeError("Handler not initialised") - - return self._controller - - async def update(self, attr: AttrR): - response = await self.controller.connection.send_query("ID?\r\n") - value = response.strip("\r\n") - - await attr.set(value) +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions class TemperatureController(Controller): - device_id = AttrR(String(), handler=IDUpdater()) + device_id = AttrR(String()) def __init__(self, settings: IPConnectionSettings): super().__init__() @@ -47,8 +22,16 @@ async def connect(self): await self.connection.connect(self._ip_settings) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static07.py b/docs/snippets/static07.py index 34e2da8d4..0d1ae3dfe 100644 --- a/docs/snippets/static07.py +++ b/docs/snippets/static07.py @@ -1,19 +1,19 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import Path from fastcs.attributes import AttrHandlerR, AttrR from fastcs.connections import IPConnection, IPConnectionSettings from fastcs.controller import BaseController, Controller -from fastcs.datatypes import Float, String +from fastcs.datatypes import String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions @dataclass -class TemperatureControllerUpdater(AttrHandlerR): - command_name: str +class IDUpdater(AttrHandlerR): update_period: float | None = 0.2 _controller: TemperatureController | None = None @@ -29,17 +29,14 @@ def controller(self) -> TemperatureController: return self._controller async def update(self, attr: AttrR): - response = await self.controller.connection.send_query( - f"{self.command_name}?\r\n" - ) + response = await self.controller.connection.send_query("ID?\r\n") value = response.strip("\r\n") - await attr.set(attr.dtype(value)) + await attr.set(value) class TemperatureController(Controller): - device_id = AttrR(String(), handler=TemperatureControllerUpdater("ID")) - power = AttrR(Float(), handler=TemperatureControllerUpdater("P")) + device_id = AttrR(String(), handler=IDUpdater()) def __init__(self, settings: IPConnectionSettings): super().__init__() @@ -51,8 +48,16 @@ async def connect(self): await self.connection.connect(self._ip_settings) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static08.py b/docs/snippets/static08.py index b865f6857..cf45e1b29 100644 --- a/docs/snippets/static08.py +++ b/docs/snippets/static08.py @@ -1,19 +1,19 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any +from pathlib import Path -from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW +from fastcs.attributes import AttrHandlerR, AttrR from fastcs.connections import IPConnection, IPConnectionSettings from fastcs.controller import BaseController, Controller from fastcs.datatypes import Float, String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions @dataclass -class TemperatureControllerHandler(AttrHandlerRW): +class TemperatureControllerUpdater(AttrHandlerR): command_name: str update_period: float | None = 0.2 _controller: TemperatureController | None = None @@ -37,16 +37,10 @@ async def update(self, attr: AttrR): await attr.set(attr.dtype(value)) - async def put(self, attr: AttrW, value: Any): - await self.controller.connection.send_command( - f"{self.command_name}={value}\r\n" - ) - class TemperatureController(Controller): - device_id = AttrR(String(), handler=TemperatureControllerHandler("ID")) - power = AttrR(Float(), handler=TemperatureControllerHandler("P")) - ramp_rate = AttrRW(Float(), handler=TemperatureControllerHandler("R")) + device_id = AttrR(String(), handler=TemperatureControllerUpdater("ID")) + power = AttrR(Float(), handler=TemperatureControllerUpdater("P")) def __init__(self, settings: IPConnectionSettings): super().__init__() @@ -58,8 +52,16 @@ async def connect(self): await self.connection.connect(self._ip_settings) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static09.py b/docs/snippets/static09.py index 49b6e2702..df072cc82 100644 --- a/docs/snippets/static09.py +++ b/docs/snippets/static09.py @@ -1,29 +1,30 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import Path from typing import Any from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW from fastcs.connections import IPConnection, IPConnectionSettings -from fastcs.controller import BaseController, Controller, SubController -from fastcs.datatypes import Float, Int, String +from fastcs.controller import BaseController, Controller +from fastcs.datatypes import Float, String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions @dataclass class TemperatureControllerHandler(AttrHandlerRW): command_name: str update_period: float | None = 0.2 - _controller: TemperatureController | TemperatureRampController | None = None + _controller: TemperatureController | None = None async def initialise(self, controller: BaseController): - assert isinstance(controller, TemperatureController | TemperatureRampController) + assert isinstance(controller, TemperatureController) self._controller = controller @property - def controller(self) -> TemperatureController | TemperatureRampController: + def controller(self) -> TemperatureController: if self._controller is None: raise RuntimeError("Handler not initialised") @@ -31,7 +32,7 @@ def controller(self) -> TemperatureController | TemperatureRampController: async def update(self, attr: AttrR): response = await self.controller.connection.send_query( - f"{self.command_name}{self.controller.suffix}?\r\n" + f"{self.command_name}?\r\n" ) value = response.strip("\r\n") @@ -39,47 +40,35 @@ async def update(self, attr: AttrR): async def put(self, attr: AttrW, value: Any): await self.controller.connection.send_command( - f"{self.command_name}{self.controller.suffix}={value}\r\n" + f"{self.command_name}={attr.dtype(value)}\r\n" ) -class TemperatureRampController(SubController): - start = AttrRW(Int(), handler=TemperatureControllerHandler("S")) - end = AttrRW(Int(), handler=TemperatureControllerHandler("E")) - - def __init__(self, index: int, connection: IPConnection): - self.suffix = f"{index:02d}" - - super().__init__(f"Ramp{self.suffix}") - - self.connection = connection - - class TemperatureController(Controller): device_id = AttrR(String(), handler=TemperatureControllerHandler("ID")) power = AttrR(Float(), handler=TemperatureControllerHandler("P")) ramp_rate = AttrRW(Float(), handler=TemperatureControllerHandler("R")) - suffix = "" - - def __init__(self, ramp_count: int, settings: IPConnectionSettings): + def __init__(self, settings: IPConnectionSettings): super().__init__() self._ip_settings = settings self.connection = IPConnection() - self._ramp_controllers: list[TemperatureRampController] = [] - for idx in range(1, ramp_count + 1): - ramp_controller = TemperatureRampController(idx, self.connection) - self._ramp_controllers.append(ramp_controller) - self.register_sub_controller(f"R{idx}", ramp_controller) - async def connect(self): await self.connection.connect(self._ip_settings) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) -fastcs = FastCS(TemperatureController(4, connection_settings), [epics_options]) +fastcs = FastCS(TemperatureController(connection_settings), [epics_options]) + +fastcs.create_gui() # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static10.py b/docs/snippets/static10.py index d4b1b1f63..919254547 100644 --- a/docs/snippets/static10.py +++ b/docs/snippets/static10.py @@ -1,16 +1,16 @@ from __future__ import annotations -import enum from dataclasses import dataclass +from pathlib import Path from typing import Any from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW from fastcs.connections import IPConnection, IPConnectionSettings from fastcs.controller import BaseController, Controller, SubController -from fastcs.datatypes import Enum, Float, Int, String +from fastcs.datatypes import Float, Int, String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions @dataclass @@ -40,19 +40,13 @@ async def update(self, attr: AttrR): async def put(self, attr: AttrW, value: Any): await self.controller.connection.send_command( - f"{self.command_name}{self.controller.suffix}={value}\r\n" + f"{self.command_name}{self.controller.suffix}={attr.dtype(value)}\r\n" ) -class OnOffEnum(enum.StrEnum): - Off = "0" - On = "1" - - class TemperatureRampController(SubController): start = AttrRW(Int(), handler=TemperatureControllerHandler("S")) end = AttrRW(Int(), handler=TemperatureControllerHandler("E")) - enabled = AttrRW(Enum(OnOffEnum), handler=TemperatureControllerHandler("N")) def __init__(self, index: int, connection: IPConnection): self.suffix = f"{index:02d}" @@ -85,8 +79,16 @@ async def connect(self): await self.connection.connect(self._ip_settings) -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(4, connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static11.py b/docs/snippets/static11.py index b8bdcd4ad..fb56b129a 100644 --- a/docs/snippets/static11.py +++ b/docs/snippets/static11.py @@ -1,8 +1,8 @@ from __future__ import annotations import enum -import json from dataclasses import dataclass +from pathlib import Path from typing import Any from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW @@ -11,8 +11,7 @@ from fastcs.datatypes import Enum, Float, Int, String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions -from fastcs.wrappers import scan +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions @dataclass @@ -42,7 +41,7 @@ async def update(self, attr: AttrR): async def put(self, attr: AttrW, value: Any): await self.controller.connection.send_command( - f"{self.command_name}{self.controller.suffix}={value}\r\n" + f"{self.command_name}{self.controller.suffix}={attr.dtype(value)}\r\n" ) @@ -55,9 +54,6 @@ class TemperatureRampController(SubController): start = AttrRW(Int(), handler=TemperatureControllerHandler("S")) end = AttrRW(Int(), handler=TemperatureControllerHandler("E")) enabled = AttrRW(Enum(OnOffEnum), handler=TemperatureControllerHandler("N")) - target = AttrR(Float(), handler=TemperatureControllerHandler("T")) - actual = AttrR(Float(), handler=TemperatureControllerHandler("A")) - voltage = AttrR(Float()) def __init__(self, index: int, connection: IPConnection): self.suffix = f"{index:02d}" @@ -89,17 +85,17 @@ def __init__(self, ramp_count: int, settings: IPConnectionSettings): async def connect(self): await self.connection.connect(self._ip_settings) - @scan(0.1) - async def update_voltages(self): - voltages = json.loads( - (await self.connection.send_query("V?\r\n")).strip("\r\n") - ) - for index, controller in enumerate(self._ramp_controllers): - await controller.voltage.set(float(voltages[index])) - -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(4, connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static12.py b/docs/snippets/static12.py index 8bca6571e..b14b1d198 100644 --- a/docs/snippets/static12.py +++ b/docs/snippets/static12.py @@ -1,9 +1,9 @@ from __future__ import annotations -import asyncio import enum import json from dataclasses import dataclass +from pathlib import Path from typing import Any from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW @@ -12,13 +12,13 @@ from fastcs.datatypes import Enum, Float, Int, String from fastcs.launch import FastCS from fastcs.transport.epics.ca.options import EpicsCAOptions -from fastcs.transport.epics.options import EpicsIOCOptions -from fastcs.wrappers import command, scan +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions +from fastcs.wrappers import scan @dataclass class TemperatureControllerHandler(AttrHandlerRW): - name: str + command_name: str update_period: float | None = 0.2 _controller: TemperatureController | TemperatureRampController | None = None @@ -33,18 +33,18 @@ def controller(self) -> TemperatureController | TemperatureRampController: return self._controller - async def put(self, attr: AttrW, value: Any) -> None: - await self.controller.connection.send_command( - f"{self.name}{self.controller.suffix}={attr.dtype(value)}\r\n" - ) - - async def update(self, attr: AttrR) -> None: + async def update(self, attr: AttrR): response = await self.controller.connection.send_query( - f"{self.name}{self.controller.suffix}?\r\n" + f"{self.command_name}{self.controller.suffix}?\r\n" ) - response = response.strip("\r\n") + value = response.strip("\r\n") + + await attr.set(attr.dtype(value)) - await attr.set(attr.dtype(response)) + async def put(self, attr: AttrW, value: Any): + await self.controller.connection.send_command( + f"{self.command_name}{self.controller.suffix}={attr.dtype(value)}\r\n" + ) class OnOffEnum(enum.StrEnum): @@ -98,16 +98,17 @@ async def update_voltages(self): for index, controller in enumerate(self._ramp_controllers): await controller.voltage.set(float(voltages[index])) - @command() - async def disable_all(self) -> None: - for rc in self._ramp_controllers: - await rc.enabled.process(OnOffEnum.Off) - # TODO: The requests all get concatenated and the sim doesn't handle it - await asyncio.sleep(0.1) - -epics_options = EpicsCAOptions(ca_ioc=EpicsIOCOptions(pv_prefix="DEMO")) +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) connection_settings = IPConnectionSettings("localhost", 25565) fastcs = FastCS(TemperatureController(4, connection_settings), [epics_options]) +fastcs.create_gui() + # fastcs.run() # Commented as this will block diff --git a/docs/snippets/static13.py b/docs/snippets/static13.py new file mode 100644 index 000000000..74e2e1a64 --- /dev/null +++ b/docs/snippets/static13.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import asyncio +import enum +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from fastcs.attributes import AttrHandlerRW, AttrR, AttrRW, AttrW +from fastcs.connections import IPConnection, IPConnectionSettings +from fastcs.controller import BaseController, Controller, SubController +from fastcs.datatypes import Enum, Float, Int, String +from fastcs.launch import FastCS +from fastcs.transport.epics.ca.options import EpicsCAOptions +from fastcs.transport.epics.options import EpicsGUIOptions, EpicsIOCOptions +from fastcs.wrappers import command, scan + + +@dataclass +class TemperatureControllerHandler(AttrHandlerRW): + command_name: str + update_period: float | None = 0.2 + _controller: TemperatureController | TemperatureRampController | None = None + + async def initialise(self, controller: BaseController): + assert isinstance(controller, TemperatureController | TemperatureRampController) + self._controller = controller + + @property + def controller(self) -> TemperatureController | TemperatureRampController: + if self._controller is None: + raise RuntimeError("Handler not initialised") + + return self._controller + + async def update(self, attr: AttrR) -> None: + response = await self.controller.connection.send_query( + f"{self.command_name}{self.controller.suffix}?\r\n" + ) + value = response.strip("\r\n") + + await attr.set(attr.dtype(value)) + + async def put(self, attr: AttrW, value: Any) -> None: + await self.controller.connection.send_command( + f"{self.command_name}{self.controller.suffix}={attr.dtype(value)}\r\n" + ) + + +class OnOffEnum(enum.StrEnum): + Off = "0" + On = "1" + + +class TemperatureRampController(SubController): + start = AttrRW(Int(), handler=TemperatureControllerHandler("S")) + end = AttrRW(Int(), handler=TemperatureControllerHandler("E")) + enabled = AttrRW(Enum(OnOffEnum), handler=TemperatureControllerHandler("N")) + target = AttrR(Float(), handler=TemperatureControllerHandler("T")) + actual = AttrR(Float(), handler=TemperatureControllerHandler("A")) + voltage = AttrR(Float()) + + def __init__(self, index: int, connection: IPConnection): + self.suffix = f"{index:02d}" + + super().__init__(f"Ramp{self.suffix}") + + self.connection = connection + + +class TemperatureController(Controller): + device_id = AttrR(String(), handler=TemperatureControllerHandler("ID")) + power = AttrR(Float(), handler=TemperatureControllerHandler("P")) + ramp_rate = AttrRW(Float(), handler=TemperatureControllerHandler("R")) + + suffix = "" + + def __init__(self, ramp_count: int, settings: IPConnectionSettings): + super().__init__() + + self._ip_settings = settings + self.connection = IPConnection() + + self._ramp_controllers: list[TemperatureRampController] = [] + for idx in range(1, ramp_count + 1): + ramp_controller = TemperatureRampController(idx, self.connection) + self._ramp_controllers.append(ramp_controller) + self.register_sub_controller(f"R{idx}", ramp_controller) + + async def connect(self): + await self.connection.connect(self._ip_settings) + + @scan(0.1) + async def update_voltages(self): + voltages = json.loads( + (await self.connection.send_query("V?\r\n")).strip("\r\n") + ) + for index, controller in enumerate(self._ramp_controllers): + await controller.voltage.set(float(voltages[index])) + + @command() + async def disable_all(self) -> None: + for rc in self._ramp_controllers: + await rc.enabled.process(OnOffEnum.Off) + # TODO: The requests all get concatenated and the sim doesn't handle it + await asyncio.sleep(0.1) + + +gui_options = EpicsGUIOptions( + output_path=Path(".") / "demo.bob", title="Demo Temperature Controller" +) +epics_options = EpicsCAOptions( + gui=gui_options, + ca_ioc=EpicsIOCOptions(pv_prefix="DEMO"), +) +connection_settings = IPConnectionSettings("localhost", 25565) +fastcs = FastCS(TemperatureController(4, connection_settings), [epics_options]) + +fastcs.create_gui() + +# fastcs.run() # Commented as this will block diff --git a/docs/tutorials/static-drivers.md b/docs/tutorials/static-drivers.md index 198336e27..c478e02d0 100644 --- a/docs/tutorials/static-drivers.md +++ b/docs/tutorials/static-drivers.md @@ -2,11 +2,12 @@ ## Demo Simulation -Within FastCS there is a tickit simulation of a temperature controller. This can be run -with the `Temp Controller Sim` launch config by typing `Ctrl+P debug ` (note the -trailing whitespace), selecting the launch config and pressing enter. The simulation -will then sit and wait for commands to be sent. When it receives commands, it will log -them to the console to show what it is doing. +Within FastCS there is a tickit simulation of a temperature controller. Clone the FastCS +repository and open it in VS Code. The simulation can be run with the +`Temp Controller Sim` launch config by typing `Ctrl+P debug ` (note the trailing +whitespace), selecting the launch config and pressing enter. The simulation will then +sit and wait for commands to be sent. When it receives commands, it will log them to the +console to show what it is doing. :::{note} FastCS must be installed with the `demo` extra for the demo simulator to run. This can @@ -72,10 +73,6 @@ Now the controller has a property that will appear in the API, but the applicati completes immediately because there are no transports being run on the event loop to expose an API. -Now that the controller has an attribute, it would be useful to open the generated UI. -A `demo.bob` will have been created in the directory the application was run from. This -can be launched with Phoebus. - ## FastCS Transports FastCS supports multiple transports to expose the API of the loaded `Controller`. The @@ -100,6 +97,11 @@ passing it in. :::: +:::{warning} +In the above snippet and all hereafter, the final line is commented out. This is done to +avoid blocking our unit tests - in your own code, it should remain uncommented. +::: + The application will now run until it is stopped. There will also be a `DEMO:DeviceId` PV being served by the application. However, the record is unset because the `Controller` is not yet querying the simulator for the value. @@ -109,6 +111,20 @@ PV being served by the application. However, the record is unset because the DEMO:DeviceId ``` +Now that the controller has a PV, it would be useful to open a UI. Add EPICS GUI +options to the transport options and generate a `demo.bob` file to use with Phoebus. + +::::{admonition} Code 5 +:class: dropdown, hint + +:::{literalinclude} /snippets/static05.py +:emphasize-lines: 1,7,15-21,24 +::: + +:::: + +The `demo.bob` will have been created in the directory the application was run from. + ## FastCS Device Connection The `Attributes` of a FastCS `Controller` need some IO with the device in order to get @@ -125,11 +141,11 @@ ensure the connection is established before it is used. The simulator control connection is on port 25565. ::: -::::{admonition} Code 5 +::::{admonition} Code 6 :class: dropdown, hint -:::{literalinclude} /snippets/static05.py -:emphasize-lines: 2,13-20,24,25 +:::{literalinclude} /snippets/static06.py +:emphasize-lines: 4,15-22,32-33 ::: :::: @@ -148,11 +164,11 @@ instance of the `Updater` to the `device_id` attribute. The `update_period` property tells the base class how often to call `update` ::: -::::{admonition} Code 6 +::::{admonition} Code 7 :class: dropdown, hint -:::{literalinclude} /snippets/static06.py -:emphasize-lines: 1-3,14-34,38 +:::{literalinclude} /snippets/static07.py +:emphasize-lines: 1-3,6,8,15-35,39 ::: :::: @@ -190,11 +206,11 @@ because the value is actually a string, but for `P` the value is a float, so the etc. :::: -:::{admonition} Code 7 +:::{admonition} Code 8 :class: dropdown, hint -:::{literalinclude} /snippets/static07.py -:emphasize-lines: 8,15-16,32-34,42 +:::{literalinclude} /snippets/static08.py +:emphasize-lines: 9,16-17,33-35,38,42-43 ::: :::: @@ -215,11 +231,11 @@ The set commands do not return a response, so use the `send_command` method inst `send_query`. ::: -::::{admonition} Code 8 +::::{admonition} Code 9 :class: dropdown, hint -:::{literalinclude} /snippets/static08.py -:emphasize-lines: 4,6,16,40-43,49 +:::{literalinclude} /snippets/static09.py +:emphasize-lines: 5,7,17,41-44, 48-50 ::: :::: @@ -260,11 +276,11 @@ commands so that it can be shared between the parent `TemperatureController` and argument to define how many ramps there are, which is used to register the correct number of ramp controllers with the parent. -::::{admonition} Code 9 +::::{admonition} Code 10 :class: dropdown, hint -:::{literalinclude} /snippets/static09.py -:emphasize-lines: 8,9,19,22,26,34,42,46-57,62-63,71-75,83 +:::{literalinclude} /snippets/static10.py +:emphasize-lines: 9,10,20,23,27,35,43,47-56,64,66,72-76,90 ::: :::: @@ -285,11 +301,11 @@ enable (and disable) the ramping. Add an `AttrRW` to the `TemperatureRampController`s with an `Enum` type, using a `StrEnum` with states `Off` and `On`. -::::{admonition} Code 10 +::::{admonition} Code 11 :class: dropdown, hint -:::{literalinclude} /snippets/static10.py -:emphasize-lines: 3,10,47-51,55 +:::{literalinclude} /snippets/static11.py +:emphasize-lines: 3,11,48-50,56 ::: :::: @@ -338,11 +354,11 @@ handler. Then add a method to the `TemperatureController` with a `@scan` decorat gets the array of voltages and sets each ramp controller with its value. Also add `AttrR`s for the target and actual temperature for each ramp as described above. -::::{admonition} Code 11 +::::{admonition} Code 12 :class: dropdown, hint -:::{literalinclude} /snippets/static11.py -:emphasize-lines: 4,58-60,91-98 +:::{literalinclude} /snippets/static12.py +:emphasize-lines: 4,16,59-61,93-99 ::: :::: @@ -359,11 +375,11 @@ attribute does. Add a method with a `@command` decorator to set enabled to false in every ramp controller. -::::{admonition} Code 12 +::::{admonition} Code 13 :class: dropdown, hint -:::{literalinclude} /snippets/static12.py -:emphasize-lines: 3,16,100-106 +:::{literalinclude} /snippets/static13.py +:emphasize-lines: 3,17,102-108 ::: ::::