๐ New
* [131](https://github.com/sdss/jaeger/issues/131) **Breaking change**. This version includes a major rewrite of the internals of `Command` and how it is used throughout `jaeger`. In addition to acception a single `positioner_id`, `Command` can now receive a list of positioners to command. When the command is awaited it will wait until all the positioners have replied or the command has timed out. For the most part this is equivalent to using the old `FPS.send_to_all()` which has now been deprecated, but with the advantage that a single `Future` is created. This seems to significantly decrease the overhead that `asyncio` introduces when creating and await many tasks. `FPS.send_command()` now also accepts a list of positioners, thus replacing `send_to_all()`. For the most part low level initialisation of commands, as long as they are used to address a single positioner, should not have changed. To address multiple positioners at once use `send_command()`.
* [127](https://github.com/sdss/jaeger/issues/127) Implemented positioner LED on/off commands.
* [128](https://github.com/sdss/jaeger/issues/128) Deprecated the use of `python-can` buses since they block in a non-asynchronous way. This caused significant inefficiencies when controller >200 robots, especially on computers with old CPUs. This PR implements the major changes, including refacting `JaegerCAN` and `FPS` to initialise the buses asynchronously, and a reimplementation of `CANNetBus`, `VirtualBus`, and `Notifier`. This PR also includes some general performance gains such as a better implementation of `parse_identifier`.
* [134](https://github.com/sdss/jaeger/issues/134) Added a new actor command `reload` that will reinitialise the `FPS` instance and reload any new robots after a sextant power cycle.
* [142](https://github.com/sdss/jaeger/issues/142) Added an `ieb info` actor command to show information about the IEB layout to users.
* [119](https://github.com/sdss/jaeger/issues/119) Allow to manually add and initialise a single positioner.
โจ Improved
* [135](https://github.com/sdss/jaeger/issues/135) Cleaned up initialisation methods for `JaegerCAN` and `FPS`. Objects can now be instantiated and initialised at the same time using the async classmethod `.create()`.
* [141](https://github.com/sdss/jaeger/issues/141) The `jaeger upgrade-firmware` command will now upgrade the firmware of one sextant at a time to avoid powering on too many power supplies at the same time.
* [124](https://github.com/sdss/jaeger/issues/124) Collisions are handled better. If a move command is running when the FPS is locked, the command is cancelled. `Postioner.goto()` and `send_trajectory()` now continuously check if the FPS has been locked during the move. If it is, they fail in a non-verbose way. `FPS.send_trajectory()` now logs an error but doesn't raise an exception if the trajectory fails.
๐งน Cleaned
* [129](https://github.com/sdss/jaeger/issues/129) Removed the use of the database and predefined layouts for the FPS. Default mode is that positioners are always auto-discovered.
* [133](https://github.com/sdss/jaeger/issues/133) Completely removed the use of `python-can`. A conditional import is done for the `slcan` and `socketcan` interfaces for which `python-can` does need to be installed.
* [130](https://github.com/sdss/jaeger/issues/130) Removed engineering mode.
* [132](https://github.com/sdss/jaeger/issues/132) Merged `JagerCAN._send_commands()` and `.send_to_interfaces()` into `JaegerCAN.send_commands()`. Renamed `FPS.send_command()` `synchronous` parameter to `now`.