From 8df0bad70a13efb01fed80c1f4cd318661cda860 Mon Sep 17 00:00:00 2001 From: Benjamin Labrecque Date: Thu, 2 Jul 2026 17:10:01 +0200 Subject: [PATCH] chore: add template files --- templates/app/pyproject.toml | 33 ++++++ templates/app/src/AGEBD-SERVICE-TEMPLATE.py | 108 ++++++++++++++++++ templates/app/src/__init__.py | 0 templates/ioc/AGEBD-CPCL-TEMPLATE_main.subs | 4 + .../ioc/AGEBD-CPCL-TEMPLATE_parameters.yaml | 6 + templates/ioc/TEMPLATE.template | 27 +++++ templates/ioc/startup.script | 1 + templates/qt/TEMPLATE.ui | 1 + .../systemd/AGEBD-SERVICE-TEMPLATE.service | 16 +++ 9 files changed, 196 insertions(+) create mode 100644 templates/app/pyproject.toml create mode 100644 templates/app/src/AGEBD-SERVICE-TEMPLATE.py create mode 100644 templates/app/src/__init__.py create mode 100644 templates/ioc/AGEBD-CPCL-TEMPLATE_main.subs create mode 100644 templates/ioc/AGEBD-CPCL-TEMPLATE_parameters.yaml create mode 100644 templates/ioc/TEMPLATE.template create mode 100644 templates/ioc/startup.script create mode 100644 templates/qt/TEMPLATE.ui create mode 100644 templates/systemd/AGEBD-SERVICE-TEMPLATE.service diff --git a/templates/app/pyproject.toml b/templates/app/pyproject.toml new file mode 100644 index 0000000..f675ccb --- /dev/null +++ b/templates/app/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "agebd-{{ service_name_lower }}" +version = "0.1.0" +description = "AGEBD-{{ service_name }} Service" +requires-python = ">=3.9" +dependencies = [ + "agebd==1.2.0", +] + +[dependency-groups] +dev = [ + "pytest>=8.4.2", + "ruff>=0.15.20", +] + +# This tells uv where to look for 'agebd' during LOCAL dev work +[tool.uv.sources] +# Path is relative to where agebd package is deployed on hla +# machines (see CI/CD in .gitea/workflows) +agebd = { path = "../../../../../packages/{{ '{{ agebd_env }}' }}/agebd", editable = true } + +[tool.ruff] +include = [ + "src/**/*.py", +] +line-length = 100 + +[tool.ruff.lint] +# use isort to sort imports +extend-select = ["I"] + +[tool.ruff.lint.isort] +known-first-party = ["agebd"] diff --git a/templates/app/src/AGEBD-SERVICE-TEMPLATE.py b/templates/app/src/AGEBD-SERVICE-TEMPLATE.py new file mode 100644 index 0000000..d77b9cb --- /dev/null +++ b/templates/app/src/AGEBD-SERVICE-TEMPLATE.py @@ -0,0 +1,108 @@ +from time import sleep + +import typer +from epics import dbr + +from agebd.enums import LogLevel +from agebd.pv import LocalPVLink +from agebd.pv import PVLink as PV +from agebd.runner import CallbackRunner +from agebd.service.base import BaseService +from agebd.service.pvs import BasePVs +from agebd.utils import get_pv_class, init_logging, printgetversion + +__version__ = printgetversion(__file__) + +PV = get_pv_class() + + +class PVs(BasePVs): + # Option 1: + ## Service specific PVs from dedicated IOC + my_pv1 = PV("MY_PV1") + + ## Service specific PVs from other IOCs + # ... + + def __init__(self, service_name: str, pv_factory): + super().__init__(service_name, pv_factory) + PV = pv_factory + + # TODO: can we do this cleaner? Maybe mechanism for + # settings initial values in dev mode (e.g. ini file) + if isinstance(self.onoff, LocalPVLink): + self.onoff.value = True + + # Option 2: + ## Service specific PVs from dedicated IOC + self.my_pv2 = PV("MY_PV2") + + ## Service specific PVs from other IOCs + # ... + + # TODO: class attribute names usually lowercase + # usually it is advisable to trigger the mainloop of a service on changes of certain PVs: + self.CallbackPV = PV("...", auto_monitor=dbr.DBE_VALUE) + + +class Service(BaseService[PVs]): + def __init__( + self, name: str, pvs: PVs, version: str = __version__, sleep_interval: float = 0.1 + ): + super().__init__(name, pvs, version, sleep_interval) + + # TODO: What is this for? + self.path = "sls/bd/bin" + + def update(self): + # when callback is fired, this code will be executed + if self.CallbackFired: + ####################################################################################### + ####################################################################################### + # here goes all the code that your service + # is supposed to be running when the callback is fired + sleep(1) + ####################################################################################### + ####################################################################################### + + # callback done + self.CallbackFired = 0 + + ####################################################################################### + ####################################################################################### + # here goes all the code that your service + # is supposed to be running in any case, e.g., + # update some pvs/vals + self.pvs.my_pv1.put("MY_PV1 value") + self.pvs.my_pv2.put("MY_PV2 value") + self.val1 = self.pvs.my_pv1.get() + self.val2 = self.pvs.my_pv2.get() + print(self.val1) + print(self.val2) + # maybe sleep to limit the update loop execution rate + sleep(1) + ####################################################################################### + ####################################################################################### + + +def main( + log_level: LogLevel = typer.Option( + LogLevel.INFO, + "--log-level", + "-l", + # Looks at the OS env variable first. If empty, falls back to "INFO" + envvar="SLS_SERVICE_LOG_LEVEL", + # help="Set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL", + case_sensitive=False, + ), +): + init_logging(log_level) + service_name = "{{ service_name }}" + pvs = PVs(service_name=service_name, pv_factory=PV) + service = Service(name=service_name, pvs=pvs) + runner = CallbackRunner(service=service) + runner.start() + + +if __name__ == "__main__": + typer.run(main) diff --git a/templates/app/src/__init__.py b/templates/app/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/ioc/AGEBD-CPCL-TEMPLATE_main.subs b/templates/ioc/AGEBD-CPCL-TEMPLATE_main.subs new file mode 100644 index 0000000..6c098d9 --- /dev/null +++ b/templates/ioc/AGEBD-CPCL-TEMPLATE_main.subs @@ -0,0 +1,4 @@ +file {{ service_name }}.template { + pattern { DEVICE } + { AGEBD-{{ service_name }} } +} diff --git a/templates/ioc/AGEBD-CPCL-TEMPLATE_parameters.yaml b/templates/ioc/AGEBD-CPCL-TEMPLATE_parameters.yaml new file mode 100644 index 0000000..5404d47 --- /dev/null +++ b/templates/ioc/AGEBD-CPCL-TEMPLATE_parameters.yaml @@ -0,0 +1,6 @@ +cpu_architecture: x86_64 +epics_version: 7.0.8 +ioc_host: {{ ioc_host }} +ioc_port: {{ ioc_port }} +os: RHEL8 +os_id: rhel diff --git a/templates/ioc/TEMPLATE.template b/templates/ioc/TEMPLATE.template new file mode 100644 index 0000000..59bd9ae --- /dev/null +++ b/templates/ioc/TEMPLATE.template @@ -0,0 +1,27 @@ +# This is a template for preaparing an IOC providing BD Service specific PVs +# Access Security Groups: +# DEFAULT - logt wenn EGU oder so veraendert werden +# LOG - logt wenn neuer wert geschrieben wird +# PROTECTED - verhindert versuchte caputs auf alle fields ausser VAL +# READONLY - verhindert versuchte caputs und aktiviert caputlog +# Input links can specify CA, CP, or CPP. +# If the input link specifies CA, it will be forced to be a Channel Access link. +# If the input link specifies CP, it will also be forced to be a Channel Access link; in addition, it will cause the record containing the input link to process whenever a monitor is posted, no matter what the record's SCAN field specifies. +# If the input link specifies CPP, it means the same thing as CP, except that the record will be processed if and only if the record itself specifies passive in its SCAN field. Output links can specify CA, which will simply cause them to be Channel Access links. +# + +record(ao, "$(DEVICE):AO") { + field(DESC, "Analog Output Record (ao)") + field(ASG, "PROTECTED") + field(VAL, "0") + field(PINI, "YES") +} + +record(bo, "$(DEVICE):BO") { + field(DESC, "Binary Output Record (bo)") + field(ASG, "READONLY") + field(ZSV, "MINOR") + field(OSV, "NO_ALARM") + field(VAL, "1") + field(PINI, "YES") +} \ No newline at end of file diff --git a/templates/ioc/startup.script b/templates/ioc/startup.script new file mode 100644 index 0000000..ffbabc8 --- /dev/null +++ b/templates/ioc/startup.script @@ -0,0 +1 @@ +epicsEnvSet("ENGINEER", "{{ user }}") diff --git a/templates/qt/TEMPLATE.ui b/templates/qt/TEMPLATE.ui new file mode 100644 index 0000000..4640904 --- /dev/null +++ b/templates/qt/TEMPLATE.ui @@ -0,0 +1 @@ +# TODO diff --git a/templates/systemd/AGEBD-SERVICE-TEMPLATE.service b/templates/systemd/AGEBD-SERVICE-TEMPLATE.service new file mode 100644 index 0000000..7d97560 --- /dev/null +++ b/templates/systemd/AGEBD-SERVICE-TEMPLATE.service @@ -0,0 +1,16 @@ +[Unit] +Description=AGEBD-SERVICE-{{ service_name }} +After=network.target + +[Service] +Environment=AGEBD_ENV={{ '{{ agebd_env }}' }} +ExecStart=/sls/bd/hla/bin/start_service.sh {{ service_id }} {{ service_name }} +estart=no + +[Install] +WantedBy=default.target + +# https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html +# systemctl --user daemon-reload +# systemctl --user enable /sls/bd/bin/systemd/AGEBD-SERVICE-{{ service_name }}.service +# systemctl --user start AGEBD-SERVICE-{{ service_name }}