From 167636de000920e55afb0df76ad0bdddd64de4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 7 Aug 2023 14:21:41 +0200 Subject: [PATCH] docs: adding Readme section explaining units --- README.md | 68 ++++++++++++++++++++++++++++++++++++++ docs/images/Units_App.png | Bin 0 -> 19987 bytes 2 files changed, 68 insertions(+) create mode 100644 docs/images/Units_App.png diff --git a/README.md b/README.md index 66b2bfb..9c3174f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - [Connecting to the Service using rpyc](#connecting-to-the-service-using-rpyc) - [Understanding Service Persistence](#understanding-service-persistence) - [Understanding Tasks in pydase](#understanding-tasks-in-pydase) +- [Understanding Units in pydase](#understanding-units-in-pydase) - [Documentation](#documentation) - [Contributing](#contributing) - [License](#license) @@ -22,6 +23,7 @@ * [Support for `rpyc` connections, allowing for programmatic control and interaction with your service](#connecting-to-the-service-using-rpyc) * [Saving and restoring the service state for service persistence](#understanding-service-persistence) * [Automated task management with built-in start/stop controls and optional autostart](#understanding-tasks-in-pydase) +* [Support for units](#understanding-units-in-pydase) * Event-based callback functionality for real-time updates * Support for additional servers for specific use-cases @@ -205,6 +207,72 @@ In this example, `read_sensor_data` is a task that continuously reads data from By listing it in the `_autostart_tasks` dictionary, it will automatically start running when `Server(service).run()` is executed. As with all tasks, `pydase` will also generate `start_read_sensor_data` and `stop_read_sensor_data` methods, which can be called to manually start and stop the data reading task. +## Understanding Units in pydase + +`pydase` integrates with the [`pint`](https://pint.readthedocs.io/en/stable/) package to allow you to work with physical quantities within your service. This enables you to define attributes with units, making your service more expressive and ensuring consistency in the handling of physical quantities. + +You can define quantities in your `DataService` subclass using `pydase`'s `units` functionality. These quantities can be set and accessed like regular attributes, and `pydase` will automatically handle the conversion between floats and quantities with units. + +Here's an example: + +```python +from typing import Any +from pydase import DataService, Server +import pydase.units as u + + +class ServiceClass(DataService): + voltage = 1.0 * u.units.V + _current: u.Quantity = 1.0 * u.units.mA + + @property + def current(self) -> u.Quantity: + return self._current + + @current.setter + def current(self, value: Any) -> None: + self._current = value + + +if __name__ == "__main__": + service = ServiceClass() + + # You can just set floats to the Quantity objects. The DataService __setattr__ will + # automatically convert this + service.voltage = 10.0 + service.current = 1.5 + + Server(service).run() +``` + +In the frontend, quantities are rendered as floats, with the unit displayed as additional text. This allows you to maintain a clear and consistent representation of physical quantities across both the backend and frontend of your service. +![Web interface with rendered units](./docs/images/Units_App.png) + +Should you need to access the magnitude or the unit of a quantity, you can use the `.m` attribute or the `.u` attribute of the variable, respectively. For example, this could be necessary to set the periodicity of a task: + +```python +import asyncio +from pydase import DataService, Server +import pydase.units as u + + +class ServiceClass(DataService): + readout_wait_time = 1.0 * u.units.ms + + async def read_sensor_data(self): + while True: + print("Reading out sensor ...") + await asyncio.sleep(self.readout_wait_time.to("s").m) + + +if __name__ == "__main__": + service = ServiceClass() + + Server(service).run() +``` + +For more information about what you can do with the units, please consult the documentation of [`pint`](https://pint.readthedocs.io/en/stable/). + ## Documentation The full documentation provides more detailed information about `pydase`, including advanced usage examples, API references, and tips for troubleshooting common issues. See the [full documentation](URL_TO_YOUR_DOCUMENTATION) for more information. diff --git a/docs/images/Units_App.png b/docs/images/Units_App.png new file mode 100644 index 0000000000000000000000000000000000000000..01a2257cfb6a2278451a97e4c74843c35ff5f67f GIT binary patch literal 19987 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 II;Vst00>A>k^lez literal 0 HcmV?d00001