diff --git a/doc/_quarto.yml b/doc/_quarto.yml index b9bd75f0..ff754c8f 100644 --- a/doc/_quarto.yml +++ b/doc/_quarto.yml @@ -27,7 +27,8 @@ website: - section: User Tutorials file: ./ioc/tutorials.md contents: - - ./ioc/tutorials/getting-started.md + - ./ioc/tutorials/pre-requisites.md + - ./ioc/tutorials/streamdevice.md - ./ioc/tutorials/porting.md - ./ioc/tutorials/day-to-day-dev.md - ./ioc/tutorials/integration-tests.md diff --git a/doc/_vale/Vocab/EPNix/accept.txt b/doc/_vale/Vocab/EPNix/accept.txt index fe05a868..02030a90 100644 --- a/doc/_vale/Vocab/EPNix/accept.txt +++ b/doc/_vale/Vocab/EPNix/accept.txt @@ -14,6 +14,7 @@ NixOS NixOps procServ SELinux +StreamDevice systemd RedHat [Uu]ntracked diff --git a/doc/ioc/guides/flake-registry.md b/doc/ioc/guides/flake-registry.md index bc7c3b72..23483acd 100644 --- a/doc/ioc/guides/flake-registry.md +++ b/doc/ioc/guides/flake-registry.md @@ -2,19 +2,17 @@ title: Setting up the flake registry --- -Since the usage of EPNix doesn't encourage installing epics-base globally, some commonly used command-line programs won't be available in your usual environment. +While developing with EPNix, +it's possible you will end up typing `'github:epics-extensions/epnix'` quite often. -It's possible to go into a top, and type `nix develop`{.bash} just to have the `caget`{.bash} command available, but it's quite tedious. +It happens when you need to create a "top" template, +or when you just want to have `epics-base` in your shell, +and so on. -An alternative would be to run: +This is tedious. -``` bash -nix develop 'github:epics-extensions/epnix' -``` - -This will give you the development shell of EPNix itself, with the added benefit of having the latest version of EPICS base. - -The command is quite hard to remember, but with the "registry" feature of Nix, you can shorten it by running: +Nix provides a way of shortening these URLs, +by adding to the [Nix registry][]: ``` bash nix registry add epnix 'github:epics-extensions/epnix' @@ -27,8 +25,11 @@ For example, the develop command to have EPICS based installed outside of a top nix develop epnix ``` -Another benefit is that you can now initialize an EPNix top by running: +If you want to initialize an EPNix top, +you can run: ``` bash nix flake new -t epnix my-top ``` + + [Nix registry]: https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-registry.html#description diff --git a/doc/ioc/tutorials/getting-started.md b/doc/ioc/tutorials/getting-started.md deleted file mode 100644 index 138bf8e3..00000000 --- a/doc/ioc/tutorials/getting-started.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: Getting started ---- - -# Requirements - -The requirements for using EPNix are having curl, Nix, and Git installed. - -If you need to install Nix, you also need the `xz` utility, often part of the `xzip` or `xz` package. - -You *don't* need to have EPICS base installed globally, EPNix makes it available to you when you enter your top's development shell. - -# Installing Nix - -::: callout-warning -If you use a Linux distribution with SELinux, be sure to turn it off. -You can do this by adding the line `SELINUX=disabled` in `/etc/sysconfig/selinux` on distributions based on RedHat Enterprise Linux (RHEL) like CentOS, Rocky Linux, and so on. -::: - -If you don't have Nix installed, first follow the [official instructions]. - -Unless you use WSL2, use the multi-user installation, because it builds packages in an isolated environment. - - [official instructions]: https://nixos.org/download.html#download-nix - -# Enabling Nix flakes and the `nix`{.bash} command - -Because Nix flakes and the unified `nix` command are experimental features at the time of writing, you need to enable them in your `/etc/nix/nix.conf`. - -To enable this feature, add this line to your `/etc/nix/nix.conf`: - -``` ini -experimental-features = nix-command flakes -``` - -If you have installed Nix in multi-user mode, then you have to restart the Nix daemon with `systemctl restart nix-daemon.service`{.bash}. - -# Untracked files and Nix flakes - -One important thing with Nix flakes: when your flake is in a Git repository, Nix only considers files that Git tracks. - -For example, if your `flake.nix`, is in a repository, and you create a file `foobar.txt`, you must run `git add [-N] foobar.txt`{.bash} to make Nix recognize it. - -This prevents copying build products into the Nix store. - -# Concepts - -In EPNix, your IOC have mainly one important file: the `flake.nix` file. - -The `flake.nix` file is the entry point that the `nix`{.bash} command reads in order for `nix build`{.bash}, `nix flake check`{.bash}, and `nix develop`{.bash} to work. -It's also the file where you specify your other "repository" dependencies. -For example, your IOC depends on EPNix itself, and also depends on each external EPICS "app." - -The `flake.nix` file also contains the configuration of your EPNix top. -EPNix provides a list of possible options and you can [extend them yourself]. -The [Available options] page of the documentation book documents the options provided by EPNix. - - [extend them yourself]: ./adding-options.md - [Available options]: ../references/options.md - -# Creating your project - -::: callout-note -Verify that you have set up your computer so that you can clone your repositories unattended, with for example SSH keys or tokens. -If you intend to use private Git repositories, see the [Private repository setup] guide. -::: - -With EPNix, we recommend developers to version EPICS tops separate from EPICS apps. -This means that by default, Git ignores apps created with `makeBaseApp.pl`, so that you can create separate Git repositories for them. - -If you use an old system and see Git errors while creating your template, you can install a recent version of Git by running `nix-env -iA nixpkgs.git` after installing Nix. - -To kick-start an EPNix project: - -``` bash -# Create a new directory by using the EPNix template. It will create the -# aforementioned `flake.nix` which will allow you to specify your base and your -# dependencies. It does not however create your top for you, instead, it will -# provide you with an environment with EPICS base installed (see below). -nix flake new -t 'github:epics-extensions/epnix' my-top -cd my-top - -# This will make you enter a new shell, with EPICS base installed in it. -# The EPICS base version will be the one used by your top. -nix develop - -# Initializes the EPICS part of your top, and creates a new app -makeBaseApp.pl -t ioc example -# Creates a new iocBoot folder -makeBaseApp.pl -i -t ioc -p example example - -# Versioning the top. -# This is highly recommended, since this will make Nix ignore your build -# products in its sandboxed builds -git init -git add . - -# Then, create a remote repository for the Top, and push to it -... - -# Versioning the app -cd exampleApp -git init -git add . - -# Then, create a remote repository for the App, and push to it -... -``` - -Now that your EPICS app is in a remote repository, you can instruct EPNix to use your created app from the remote repository: - -Edit your top's `flake.nix` - -- Below the other inputs, add: - -``` nix -inputs.exampleApp = { - url = "git+ssh://git@your.gitlab.com/your/exampleApp.git"; - flake = false; -}; -``` - -And, under the EPNix options section: - -- add `"inputs.exampleApp"`{.nix} in the `applications.apps` list (the quotes are necessary) - -Now, Nix tracks your app from the remote repository, and tracks its Git version in the `flake.lock` file. - -You can test that your top builds by executing: `nix build -L`{.bash}. This puts a `./result` symbolic link in your top's directory containing the result of the compilation. - -::: callout-tip -As a rule, each time you edit the `flake.nix` file, or update your inputs by running `nix flake update`{.bash} or `nix flake lock`{.bash}, you should leave and re-enter your development shell (`nix develop`{.bash}). -::: - - [Private repository setup]: ../guides/private-repo-setup.md - -# Developing your IOC - - - -## Using Nix - -As said earlier, running `nix build`{.bash} compiles your IOC. -This builds your top and all your dependencies, from a clean environment, by using your apps from remote Git repositories. -Nix places the output under `./result`. - - - - -When developing your IOC, it can become cumbersome that Nix tracks the remote repository of your app. -You sometimes want to do some temporary changes to your app, and test them before committing. - -For this exact purpose, EPNix includes a handy command called `enix-local`{.bash}. -This command behaves the same as `nix`, but instead uses your apps as-is from your local directory. - -For example, if you have started your EPNix project as in the [earlier section], you should have your top and a directory `exampleApp` under it. -Therefore, if you run `nix develop`{.bash}, then `enix-local build -L`{.bash} in the development shell, Nix will build your top, with the modifications from your local `exampleApp` directory. - -The advantage of using Nix when developing is that it builds from a "cleaner" environment. -It also stores the result in the Nix store, which you can copy by using the `nix copy`{.bash} command, and test it on another machine. - - [earlier section]: #creating-your-project - -## Using standard tools - -The EPNix development shell (`nix develop`{.bash}) includes your standard build tools. -This means that after creating your project, you can use `make`{.bash} as in any other standard EPICS development. - -The difference is that Git doesn't track `configure/RELEASE.local` and `configure/CONFIG_SITE.local` files, because they contain variables necessary to build with the EPNix environment. -They contain for example the `EPICS_BASE` variable. -To add them to your top, you can run `eregen-config`{.bash}. - -::: callout-tip -As a rule, each time you edit your modules in `flake.nix`, you should leave and re-enter your development shell, and re-run `eregen-config`{.bash}. -::: - -The advantage of using the standard tools, is that the compilation is incremental. -Nix always builds a package fully, meaning it always compiles your top from scratch. -Using `make`{.bash} directly only re-compiles the modified files, at the cost of potential impurities in your build. - -# Upgrading your app version - -After you have modified, tested, committed, and pushed your app changes, you should update your top so that your app version points to the latest version. - -To do this, you can run: - -``` bash -nix flake lock --update-input exampleApp --commit-lock-file -``` - -This command updates the `exampleApp` input, and creates a Git commit for this update. - -This command also works if you want to update the `epnix` input, or the `nixpkgs` input containing the various needed packages used by EPICS. - -# Looking up the documentation - -EPNix includes documentation: it has a documentation book (what you are reading), and man pages. - -To see the documentation book, run `edoc`{.bash} in the development shell from your top directory. - -To see the man page, run `man epnix-ioc`{.bash} in the development shell from your top directory. - -# Adding dependencies - -You now have all the tools you need to have a self-contained EPICS IOC, but it's quite useful to depend on modules from the community. -EPNix provides a way to do it. - -The first step is to examine the documentation, either the `epnix-ioc(5)` man page, under the "AVAILABLE PACKAGES" section, or in the documentation book, under the "Available packages" page. - -If the package exists, you can add this bit to your `flake.nix` file. - -``` nix -support.modules = with pkgs.epnix.support; [ your_dependency ]; -``` - -If the package doesn't exist, you can try [packaging it yourself], or you can request it in the [EPNix issue tracker]. - - [packaging it yourself]: ../developer-guides/packaging.md - [EPNix issue tracker]: https://github.com/epics-extensions/EPNix/issues - -# Deploying your IOC - -To deploy your IOC, build it by using Nix. -If you are doing a production deployment, verify that you have a clean build, by not using `enix-local`, and having a clean top Git repository. - -With this, you get a `./result` symbolic link to the result in the Nix store, which you can copy, with all its dependencies, using `nix copy`. -The only prerequisite is that the remote machine has Nix installed too. - -For example, if you want to copy a built IOC to the machine `example-ioc.prod.mycompany.com`: - -``` bash -nix copy ./result --to ssh://root@example-ioc.prod.mycompany.com -``` - -This copies the build in the Nix store and every dependencies to the remote machine. - -To run the program, you can get where the build is by inspecting the symbolic link on your local machine: - -``` bash -readlink ./result -# Returns something like: -# /nix/store/7p4x6kpawrsk6mngrxi3z09bchl2vag1-epics-distribution-custom-0.0.1 -``` - -Now you can run the IOC on the remote machine. - -``` bash -/nix/store/${SHA}-epics-distribution-custom-0.0.1/bin/linux-x86_64/example -``` - - - -If you want to do automated, declarative, or more complex deployments, we highly recommend using NixOS and optionally one of its deployment tools ([NixOps], [morph], [disnix], [colmena]) . -You can also use non-NixOS hosts. - - - - [NixOps]: https://nixos.org/nixops - [morph]: https://github.com/DBCDK/morph - [disnix]: https://github.com/svanderburg/disnix - [colmena]: https://github.com/zhaofengli/colmena - -# Pitfalls - -Although tries to resemble standard EPICS development, some differences might lead to confusion. -You can find more information about this in the [FAQ]. - - - -You might also be interested in reading [Setting up the flake registry] - - [FAQ]: ../faq.md - [Setting up the flake registry]: ../guides/flake-registry.md diff --git a/doc/ioc/tutorials/pre-requisites.md b/doc/ioc/tutorials/pre-requisites.md new file mode 100644 index 00000000..6ea7a9dd --- /dev/null +++ b/doc/ioc/tutorials/pre-requisites.md @@ -0,0 +1,76 @@ +--- +title: "Pre-requisites" +--- + +The requirements for using EPNix are having curl, Nix, and Git installed, +either in a Linux system, +or in Windows' WSL2. +Nix must be configured with "flakes" enabled. + +You *don't* need to have EPICS base installed globally, +EPNix makes it available to you +when you enter your top's development shell. + +Having a global EPICS base installation shouldn't pose any issue. + +# Installing Nix + +::: callout-warning +If you use a Linux distribution with SELinux, +be sure to turn it off. +You can do this by adding the line `SELINUX=disabled` in `/etc/sysconfig/selinux` on distributions based on RedHat Enterprise Linux (RHEL) like CentOS, Rocky Linux, and so on. +::: + +If you don't have Nix installed, +first follow the [official instructions]. +Make sure to have the `xz` utility installed beforehand, +often part of the `xzip` or `xz` package. + +Unless you use WSL2, +use the multi-user installation, +because it builds packages in an isolated environment. + + [official instructions]: https://nixos.org/download.html#download-nix + +# Enabling Nix flakes and the `nix`{.bash} command + +Because Nix flakes and the unified `nix` command are experimental features at the time of writing, +you need to enable them in your `/etc/nix/nix.conf`. + +To enable this feature, +add this line to your `/etc/nix/nix.conf`: + +``` ini +experimental-features = nix-command flakes +``` + +If you have installed Nix in multi-user mode, +then you have to restart the Nix daemon by running: + +``` bash +systemctl restart nix-daemon.service +``` + +# Untracked files and Nix flakes + +One important thing with Nix flakes: +when your flake is in a Git repository, +Nix only considers files that Git tracks. + +For example, +if your `flake.nix` is in a Git repository, +and you create a file `foobar.txt`, +you must run `git add [-N] foobar.txt`{.bash} to make Nix recognize it. + +This prevents copying build products into the Nix store. + +# Git version + +If you use an old system and see Git errors when using Nix, +install a recent version of Git by running this: + +``` bash +nix-env -iA nixpkgs.git +``` + +This command installs a recent version of Git for your current user. diff --git a/doc/ioc/tutorials/streamdevice.md b/doc/ioc/tutorials/streamdevice.md new file mode 100644 index 00000000..29441736 --- /dev/null +++ b/doc/ioc/tutorials/streamdevice.md @@ -0,0 +1,294 @@ +--- +title: "Creating a StreamDevice IOC" +--- + +In this tutorial, +you're gonna learn how to create an EPICS IOC with EPNix +that communicates with a power supply, +using the [StreamDevice] support module. + + [StreamDevice]: https://paulscherrerinstitute.github.io/StreamDevice/ + +# Pre-requisites + +Verify that you have all pre-requisites installed. +If not, +follow the [Pre-requisites] section. + + [Pre-requisites]: ./pre-requisites.md + +# Running the power supply simulator + +EPNix has a power supply simulator +for you to test your IOC. + +To run it: + +``` bash +nix run 'github:epics-extensions/epnix#psu-simulator' +``` + +For the rest of the tutorial, +leave it running in a separate terminal. + +# Creating your top + +We can use these command to create an EPNix top: + +``` bash +# Initialise an EPNix top +nix flake new -t 'github:epics-extensions/epnix' my-top +cd my-top + +# Enter the EPNix development shell, that has EPICS base installed in it. +nix develop + +# Create your app and ioc boot folder +makeBaseApp.pl -t ioc example +makeBaseApp.pl -i -t ioc -p example -a linux-x86_64 Example + +# Create a git repository, and make sure all files are tracked +git init +git add . +``` + +After that, +you can already check that your top build with: + +``` bash +nix build -L +``` + +This `nix build`{.sh} command compiles your IOC, +and all its dependencies. +This makes the usual EPICS environment setup unneeded. + +If found in the official Nix cache server, +Nix downloads packages from there +instead of compiling them. + +This command puts a `./result` symbolic link in your current directory, +containing the compilation result. + +# Adding StreamDevice to the EPNix environment + +Adding dependencies to the EPNix environment happen inside the `flake.nix` file. +This file is the main entry point for specifying your build environment: +most Nix commands used here read this file to work. + +For adding StreamDevice, +change yours like so: + +``` {.diff filename="flake.nix"} + # Add one of the supported modules here: + # --- +- #support.modules = with pkgs.epnix.support; [ StreamDevice ]; ++ support.modules = with pkgs.epnix.support; [ StreamDevice ]; +``` + +Then, +leave your EPNix development shell by running `exit`{.sh}, +and re-enter it with `nix develop`{.sh}. + +Because you modified the support modules, +run `eregen-config`{.sh} to regenerate `configure/RELEASE.local`. + +With this, +your development shell has StreamDevice available, +and StreamDevice is also added in the `RELEASE.local` file. + +::: callout-tip +As a rule, +each time you edit the `flake.nix` file, +leave and re-enter your development shell (`exit`{.sh} then `nix develop`{.sh}), +and run `eregen-config`{.sh}. +::: + +# Adding StreamDevice to your EPICS app + +To add StreamDevice to your app, +make the following modifications: + +Change the `exampleApp/src/Makefile` +so that your App knows the record types of StreamDevice and its dependencies. +Also change that file so that it links to the StreamDevice library and its dependencies, +during compilation. +For example: + +``` {.makefile filename="exampleApp/src/Makefile"} +# ... + +# Include dbd files from all support applications: +example_DBD += calc.dbd +example_DBD += asyn.dbd +example_DBD += stream.dbd +example_DBD += drvAsynIPPort.dbd + +# Add all the support libraries needed by this IOC +example_LIBS += calc +example_LIBS += asyn +example_LIBS += stream + +# ... +``` + +Create the `exampleApp/Db/example.proto` file +that has the definition of the protocol. +This file tells StreamDevice what to send the power supply, +and what to expect in return. + +``` {.perl filename="exampleApp/Db/example.proto"} +Terminator = LF; + +getVoltage { + out ":VOLT?"; in "%f"; +} + +setVoltage { + out ":VOLT %f"; + @init { getVoltage; } +} +``` + +Create the `exampleApp/Db/example.db` file. +That file specifies the name, type, and properties of the Process Variables (PV) +that EPICS exposes over the network. +It also specifies how they relate to the functions written in the protocol file. + +``` {.perl filename="exampleApp/Db/example.db"} +record(ai, "${PREFIX}VOLT-RB") { + field(DTYP, "stream") + field(INP, "@example.proto getVoltage ${PORT}") +} + +record(ao, "${PREFIX}VOLT") { + field(DTYP, "stream") + field(OUT, "@example.proto setVoltage ${PORT}") +} +``` + +Change `exampleApp/Db/Makefile` +so that the EPICS build system installs `example.proto` and `example.db`: + +``` {.makefile filename="exampleApp/Db/Makefile"} +# ... + +#---------------------------------------------------- +# Create and install (or just install) into /db +# databases, templates, substitutions like this +DB += example.db +DB += example.proto + +# ... +``` + +Change your `st.cmd` file +so that it knows where to load the protocol file, +and how to connect to the remote power supply. + +``` {.csh filename="iocBoot/iocExample/st.cmd"} +#!../../bin/linux-x86_64/example + +< envPaths + +## Register all support components +dbLoadDatabase("${TOP}/dbd/example.dbd") +example_registerRecordDeviceDriver(pdbbase) + +# Where to find the protocol files +epicsEnvSet("STREAM_PROTOCOL_PATH", "${TOP}/db") +# The TCP/IP address of the power supply +drvAsynIPPortConfigure("PS1", "localhost:8727") + +## Load record instances +dbLoadRecords("${TOP}/db/example.db", "PREFIX=, PORT=PS1") + +iocInit() +``` + +And run `chmod +x iocBoot/iocExample/st.cmd` +so that you can run your command file as-is. + +You can test that your top builds by running: + +``` bash +nix build -L +``` + +You will see that your IOC does not build. +This is because we haven't told Git to track those newly added files, +and so Nix ignores them too. + +Run `git add .`{.sh} for Git and Nix to track all files, +and try a `nix build -L`{.sh} again. + +If everything goes right, +you can examine your compiled top under `./result`. + +You can observe that: + +- the `example` app is installed under `bin/` and `bin/linux-x86_64`, + and links to the correct libraries +- `example.proto` and `example.db` are installed under `db/` +- `example.dbd` is generated and installed under `dbd/` + +# Running your IOC + +To run your IOC, +build it first with `nix build -L`{.sh}, +and change directory into the `./result/iocBoot/iocExample` folder. +Then, run: + +``` bash +./st.cmd +``` + +You should see the IOC starting and connecting to `localhost:8727`. + +# Recompiling with make + +Using `nix build`{.sh} to compile your IOC each time might feel slow. +This is because Nix re-compiles your IOC from scratch each time. + +If you want a more "traditional" edit / compile / run workflow, +you can place yourself in the development shell with `nix develop`{.sh}, +and use `make` from here. + +# Next steps + +More commands are available in the power supply simulator. +To view them, +close your IOC, +and open a direct connection to the simulator: + +``` bash +nc localhost 8727 +# or +telnet localhost 8727 +``` + +You can install the `nc` command through the `netcat` package, +or you can install the `telnet` command through the `telnet` package, + +Either command opens a prompt +where you can type `help` then press enter +to view the available commands. + +Try to edit the protocol file and the database file +to add those features to your IOC. + +For more information about how to write the StreamDevice protocol, +have a look at the [Protocol Files] documentation. + +You might also be interested in reading [Setting up the flake registry] + + [Protocol Files]: https://paulscherrerinstitute.github.io/StreamDevice/protocol.html + [Setting up the flake registry]: ../guides/flake-registry.md + +# Pitfalls + +Although EPNix tries to be close to a standard EPICS development, +some differences might lead to confusion. +You can find more information about this in the [FAQ]. + + [FAQ]: ../faq.md diff --git a/pkgs/default.nix b/pkgs/default.nix index 0d1e908c..6fab40d0 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -68,5 +68,8 @@ in # EPNix specific packages book = callPackage ./book {}; manpages = callPackage ./manpages {}; + + # Documentation support packages + psu-simulator = callPackage ./doc-support/psu-simulator {}; }); } diff --git a/pkgs/doc-support/psu-simulator/default.nix b/pkgs/doc-support/psu-simulator/default.nix new file mode 100644 index 00000000..2f233396 --- /dev/null +++ b/pkgs/doc-support/psu-simulator/default.nix @@ -0,0 +1,14 @@ +{ + poetry2nix, + lib, + epnixLib, +}: +poetry2nix.mkPoetryApplication { + projectDir = ./.; + + meta = { + homepage = "https://epics-extensions.github.io/EPNix/"; + license = lib.licenses.asl20; + maintainers = with epnixLib.maintainers; [minijackson]; + }; +} diff --git a/pkgs/doc-support/psu-simulator/poetry.lock b/pkgs/doc-support/psu-simulator/poetry.lock new file mode 100644 index 00000000..fde1b804 --- /dev/null +++ b/pkgs/doc-support/psu-simulator/poetry.lock @@ -0,0 +1,83 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.7.0" +content-hash = "1e70b172657bdbef13c5d18991199035640ab2ff4e638ec047db7a4c7a18602c" diff --git a/pkgs/doc-support/psu-simulator/psu_simulator/__init__.py b/pkgs/doc-support/psu-simulator/psu_simulator/__init__.py new file mode 100644 index 00000000..cae3015b --- /dev/null +++ b/pkgs/doc-support/psu-simulator/psu_simulator/__init__.py @@ -0,0 +1,206 @@ +"""A simple power supply simulator.""" + +import logging +import random +import socketserver + +import click + +__version__ = "0.1.0" + +# We don't need cryptographically secure RNG +# ruff: noqa: S311 + +logging.basicConfig(level="INFO", format="%(levelname)s %(message)s") +logger = logging.getLogger(__package__) + +class Server(socketserver.ThreadingMixIn, socketserver.TCPServer): + """TCP server.""" + + allow_reuse_address = True + + current: int = 0 + voltage: int = 0 + resistance: int = 0 + + +class PowerSupply(socketserver.StreamRequestHandler): + """The power supply protocol handler.""" + + def set_current(self: "PowerSupply", val: float) -> None: + """Set the current.""" + self.server.current = val + self.server.voltage = self.server.current * self.server.resistance + + def set_voltage(self: "PowerSupply", val: float) -> None: + """Set the voltage.""" + self.server.voltage = val + self.server.current = self.server.voltage / self.server.resistance + + def handle(self: "PowerSupply") -> None: + """Handle incoming connections.""" + logger.info("received connection") + + self._dispatch = { + b"help": self.cmd_help, + b":idn?": self.cmd_get_identification, + b"meas:curr?": self.cmd_get_measured_current, + b":curr?": self.cmd_get_current, + b":curr": self.cmd_set_current, + b"meas:volt?": self.cmd_get_measured_voltage, + b":volt?": self.cmd_get_voltage, + b":volt": self.cmd_set_voltage, + } + + while True: + try: + args = self.rfile.readline().strip().split() + except BrokenPipeError: + return + + if args == []: + try: + self.wfile.write(b".\n") + except BrokenPipeError: + return + continue + + command = args[0].lower() + params = args[1:] + + decoded_params = [param.decode() for param in params] + logger.info("received command: %s%s", command.decode(), decoded_params) + + if command in self._dispatch: + result = self._dispatch[command](*params) + self.wfile.write(str(result).encode()) + self.wfile.write(b"\n") + else: + self.wfile.write(f"command not found: {command.decode()}\n".encode()) + + def finish(self: "PowerSupply") -> None: + """Clean up connections.""" + logger.info("closed connection") + + def cmd_help(self: "PowerSupply", *args: str) -> str: + """Get help about various commands. + + Usage: help . + """ + if len(args) >= 1: + command = args[0] + if command in self._dispatch: + doc = self._dispatch[command].__doc__ + self.wfile.write(doc.encode()) + else: + self.wfile.write(f"command not found: {command!s}".encode()) + return "" + + self.wfile.write(b"Available commands:\n") + for command, func in self._dispatch.items(): + doc = func.__doc__.splitlines()[0].encode() + self.wfile.write(b" - '" + command + b"': " + doc + b"\n") + + return "" + + def cmd_get_identification(self: "PowerSupply", *_args: str) -> int: + """Return the identification of the power supply. + + Usage: :idn? + Returns: string + """ + return f"psu-simulator {__version__}" + + def cmd_get_measured_current(self: "PowerSupply", *_args: str) -> int: + """Return the measured current, in Amps. + + Usage: meas:curr? + Returns: float + """ + return self.server.current + random.uniform(-1.5, 1.5) + + def cmd_get_current(self: "PowerSupply", *_args: str) -> int: + """Return the current current command, in Amps. + + Usage: :curr? + Returns: float + """ + return self.server.current + + def cmd_set_current(self: "PowerSupply", *args: str) -> str: + """Set the current, in Amps. + + Usage: :curr + Returns: 'OK' | 'ERR' + """ + try: + val = float(args[0]) + except ValueError: + return "ERR" + else: + self.set_current(val) + return "OK" + + def cmd_get_measured_voltage(self: "PowerSupply", *_args: str) -> int: + """Return the measured voltage, in Volts. + + Usage: meas:volt? + Returns: float + """ + return self.server.voltage + random.uniform(-1.5, 1.5) + + def cmd_get_voltage(self: "PowerSupply", *_args: str) -> int: + """Return the voltage voltage command, in Volts. + + Usage: :volt? + Returns: float + """ + return self.server.voltage + + def cmd_set_voltage(self: "PowerSupply", *args: str) -> str: + """Set the voltage, in Volts. + + Usage: :volt + Returns: 'OK' | 'ERR' + """ + try: + val = float(args[0]) + except ValueError: + return "ERR" + else: + self.set_voltage(val) + return "OK" + + +@click.command() +@click.option( + "-l", + "--listen-address", + default="localhost", + show_default=True, + help="Listening address", +) +@click.option( + "-p", + "--port", + default=8727, + show_default=True, + help="Listening TCP port", +) +@click.option( + "--resistance", + default=20, + show_default=True, + help="Resistance of the circuit connected to the power supply, in Ohms.", +) +def main(listen_address: str, port: int, resistance: int) -> None: + """Start a power supply simulator server.""" + with Server((listen_address, port), PowerSupply) as server: + logger.info("Listening on %s:%s", listen_address, port) + server.resistance = resistance + logger.info("Resistance is %s Ohms", resistance) + + try: + server.serve_forever() + except KeyboardInterrupt: + return diff --git a/pkgs/doc-support/psu-simulator/pyproject.toml b/pkgs/doc-support/psu-simulator/pyproject.toml new file mode 100644 index 00000000..cc599d2f --- /dev/null +++ b/pkgs/doc-support/psu-simulator/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "psu-simulator" +version = "0.1.0" +description = "A power supply simulator for the StreamDevice tutorial" +authors = ["RĂ©mi NICOLE "] + +[tool.poetry.scripts] +psu-simulator = "psu_simulator:main" + +[tool.poetry.dependencies] +python = ">=3.7.0" +click = "^8.1.7" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +select = ["ALL"] diff --git a/templates/top/.gitignore b/templates/top/.gitignore index 2ddb5492..4030d251 100644 --- a/templates/top/.gitignore +++ b/templates/top/.gitignore @@ -27,10 +27,3 @@ envPaths # Compilation database generated by bear or other compile_commands.json - -# Applications and Support modules should be an EPNix dependency in flake.nix -*App -*Sup -# You can add exceptions like this: -# --- -#!myCustomLocalApp diff --git a/templates/top/flake.nix b/templates/top/flake.nix index 9ea2c6a2..a1cb68d4 100644 --- a/templates/top/flake.nix +++ b/templates/top/flake.nix @@ -4,7 +4,8 @@ inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.epnix.url = "github:epics-extensions/epnix"; - # Add your app inputs here: + # If you have an "App" as a separate repository, + # add it as an input here: # --- #inputs.exampleApp = { # url = "git+ssh://git@my-server.org/me/exampleApp.git"; @@ -36,8 +37,8 @@ # --- #support.modules = with pkgs.epnix.support; [ StreamDevice ]; - # Add your applications: - # Note that flake inputs must be quoted in this context + # If you have an "App" as a separate repository, + # add it here: # --- #applications.apps = [ "inputs.exampleApp" ];