|
| 1 | +# The FastCS class |
| 2 | + |
| 3 | +`FastCS` is the entrypoint for a fastcs application. It connects a `Controller` to one |
| 4 | +or more `Transport`s, runs the controller's update loops as background tasks, and |
| 5 | +manages the full application lifecycle from startup to shutdown. |
| 6 | + |
| 7 | +## Construction |
| 8 | + |
| 9 | +```python |
| 10 | +from fastcs import FastCS |
| 11 | +from fastcs.controllers import Controller |
| 12 | +from fastcs.transports.epics import EpicsCATransport |
| 13 | + |
| 14 | + |
| 15 | +class MyController(Controller): |
| 16 | + pass |
| 17 | + |
| 18 | + |
| 19 | +control_system = FastCS(controller=MyController(), transports=[EpicsCATransport()]) |
| 20 | + |
| 21 | +control_system.run() |
| 22 | +``` |
| 23 | + |
| 24 | +## Startup and Runtime |
| 25 | + |
| 26 | +Calling `control_system.run()` (or `await control_system.serve()`) executes the |
| 27 | +following steps in order: |
| 28 | + |
| 29 | +1. **Initialise the controller:** `controller.initialise()` is awaited, allowing the |
| 30 | + controller to query the device and dynamically add attributes before the API is |
| 31 | + built. After that, `controller.post_initialise()` is called to perform any final |
| 32 | + setup, such as validating all type hints are satisfied. |
| 33 | + |
| 34 | +2. **Build the API:** `controller.create_api_and_tasks()` returns the `ControllerAPI` |
| 35 | + that transports will use, plus two lists of coroutines: *initial tasks* (run once at |
| 36 | + startup) and *scan tasks* (run as continuous background loops). |
| 37 | + |
| 38 | +3. **Connect transports:** each transport's `connect()` method is called with the |
| 39 | + `ControllerAPI`. This lets the transport inspect the controller's attributes and |
| 40 | + commands to prepare its protocol-specific representations before serving begins. |
| 41 | + |
| 42 | +4. **Connect the controller:** `controller.connect()` is called to open the connection |
| 43 | + to the device and perform any other setup logic. |
| 44 | + |
| 45 | +5. **Run initial tasks:** each initial-task coroutine is awaited in sequence. These are |
| 46 | + `@scan(period=ONCE)` methods and `AttributeIO` update callbacks with |
| 47 | + `update_period=ONCE`. |
| 48 | + |
| 49 | +6. **Start scan tasks:** each scan task coroutine is wrapped in an `asyncio.Task` and |
| 50 | + run as a background task for the lifetime of the application. |
| 51 | + |
| 52 | +7. **Gather transport coroutines:** `asyncio.gather` runs all transport `serve()` |
| 53 | + coroutines concurrently. Each transport begins accepting and responding to protocol |
| 54 | + requests. |
| 55 | + |
| 56 | +8. **Scan pause and reconnect**: If any scan tasks raise exceptions, all scan tasks are |
| 57 | + paused until `reconnect`. |
| 58 | + |
| 59 | +## The Interactive Shell |
| 60 | + |
| 61 | +Alongside the transport coroutines, FastCS launches an embedded |
| 62 | +[IPython](https://ipython.org/) shell (unless `interactive=False` is passed). The shell |
| 63 | +namespace is pre-populated with: |
| 64 | + |
| 65 | +| Name | Value | |
| 66 | +|------|-------| |
| 67 | +| `controller` | The root controller instance | |
| 68 | +| `transports` | The class names of active transports | |
| 69 | +| `run` | A helper that schedules a coroutine on the FastCS event loop from the IPython thread | |
| 70 | +| *transport-specific keys* | Any entries exposed via each transport's `context` property | |
| 71 | + |
| 72 | +The shell runs in a separate thread so it does not block the asyncio event loop. When |
| 73 | +the user exits the shell, the application begins its shutdown sequence. |
| 74 | + |
| 75 | +When `interactive=False` a simple coroutine that blocks forever keeps the application |
| 76 | +alive until the task is cancelled externally (e.g. SIGINT). |
| 77 | + |
| 78 | +## Shutdown Sequence |
| 79 | + |
| 80 | +When then run is stopped, FastCS performs an orderly teardown: |
| 81 | + |
| 82 | +1. **Cancel scan tasks:** each background scan task is cancelled and removed, stopping |
| 83 | + all periodic polling. |
| 84 | + |
| 85 | +2. **Disconnect the controller:** `controller.disconnect()` is awaited, allowing the |
| 86 | + controller to release device resources cleanly. |
0 commit comments