diff --git a/services/shifttool/current/app/pyproject.toml b/services/shifttool/current/app/pyproject.toml index 990a34c..e5be901 100644 --- a/services/shifttool/current/app/pyproject.toml +++ b/services/shifttool/current/app/pyproject.toml @@ -6,6 +6,7 @@ requires-python = "==3.10.*" dependencies = [ "agebd", ] +# TODO: elog [dependency-groups] dev = [ diff --git a/services/shifttool/current/app/src/agebd_shifttool/main.py b/services/shifttool/current/app/src/agebd_shifttool/main.py index a96df33..01aa2ab 100644 --- a/services/shifttool/current/app/src/agebd_shifttool/main.py +++ b/services/shifttool/current/app/src/agebd_shifttool/main.py @@ -5,9 +5,9 @@ from agebd.runner import CallbackRunner from agebd.utils import init_logging from agebd_shifttool import PVs, Service - # TODO: remove: cicd test 10 + def main( log_level: LogLevel = typer.Option( LogLevel.INFO, diff --git a/services/shifttool/current/app/src/agebd_shifttool/service.py b/services/shifttool/current/app/src/agebd_shifttool/service.py index a5c4ea0..78b6503 100644 --- a/services/shifttool/current/app/src/agebd_shifttool/service.py +++ b/services/shifttool/current/app/src/agebd_shifttool/service.py @@ -1,12 +1,17 @@ +import locale import time +from datetime import datetime +from time import time -from epics import dbr +import elog -from agebd.pv import LocalPVLink +from agebd.pv import get_pv_class from agebd.service.base import BaseService from agebd.service.pvs import BasePVs from agebd.utils import printgetversion -from agebd.pv import get_pv_class + +# Setze die Locale auf Deutsch (z. B. 'de_DE' oder 'de_CH' für Schweiz) +locale.setlocale(locale.LC_TIME, "de_CH.UTF-8") # oder 'de_CH.UTF-8' __version__ = printgetversion(__file__) @@ -14,36 +19,147 @@ PV = get_pv_class() class PVs(BasePVs): - # Option 1: - ## Service specific PVs from dedicated IOC - my_pv1 = PV("AGEBD-SHIFTTOOL:AO") # TODO: is this a good PV to use? + req_MakeElog = PV("AGEOP-SHIFTTOOL:REQ-SHIFT-ENTRY.VAL") + req_UpdateTeam = PV("AGEOP-SHIFTTOOL:REQ-UPDATE-TEAM.VAL") - ## Service specific PVs from other IOCs - # ... + shift = PV("AGEOP-SHIFTTOOL:TXT-SHIFT.VAL") + crew = PV("AGEOP-SHIFTTOOL:TXT-SHIFT-CREW.VAL") + + operators = [ + PV("AGEOP-SHIFTTOOL:TXT-OPERATOR-01.VAL"), + PV("AGEOP-SHIFTTOOL:TXT-OPERATOR-02.VAL"), + PV("AGEOP-SHIFTTOOL:TXT-OPERATOR-03.VAL"), + PV("AGEOP-SHIFTTOOL:TXT-OPERATOR-04.VAL"), + PV("AGEOP-SHIFTTOOL:TXT-OPERATOR-05.VAL"), + ] + links = { + "Problem-01": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-01.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=Downtime", + ], + "Problem-02": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-02.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=Beamdrop", + ], + "Problem-03": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-03.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=Lifetime", + ], + "Problem-04": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-04.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=Beamsize", + ], + "Problem-05": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-05.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=keiner", + ], + "Problem-06": [ + PV("AGEOP-SHIFTTOOL:LINK-Problem-06.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Problem&pAutor=Rast%2C%20Streich%2C%20Weber&pEffekt=anderer", + ], + "Optimierung-01": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-01.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung%20Transfer", + ], + "Optimierung-02": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-02.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung%20Tau%2FYsig", + ], + "Optimierung-03": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-03.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung", + ], + "Optimierung-04": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-04.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung", + ], + "Optimierung-05": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-05.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung", + ], + "Optimierung-06": [ + PV("AGEOP-SHIFTTOOL:LINK-Optimierung-06.VAL"), + "https://elog-gfa.psi.ch/sls/?cmd=Neu&pEintrag=Tuning&pAutor=Rast%2C%20Streich%2C%20Weber&pTitel=Optimierung", + ], + "Piquet": [ + PV("AGEOP-SHIFTTOOL:LINK-Piquet.VAL"), + ], + "Info": [ + PV("AGEOP-SHIFTTOOL:LINK-Info.VAL"), + ], + "AktuellesProtokoll": [ + PV("AGEOP-SHIFTTOOL:LINK-AktuellesProtokoll.VAL"), + ], + "AndereEintraege-01": [ + PV("AGEOP-SHIFTTOOL:LINK-AndereEintraege-01.VAL"), + ], + "AndereEintraege-02": [ + PV("AGEOP-SHIFTTOOL:LINK-AndereEintraege-02.VAL"), + ], + "AndereEintraege-03": [ + PV("AGEOP-SHIFTTOOL:LINK-AndereEintraege-03.VAL"), + ], + "ListenAnzeigen-01": [ + PV("AGEOP-SHIFTTOOL:LINK-ListenAnzeigen-01.VAL"), + ], + "ListenAnzeigen-02": [ + PV("AGEOP-SHIFTTOOL:LINK-ListenAnzeigen-02.VAL"), + ], + "ListenAnzeigen-03": [ + PV("AGEOP-SHIFTTOOL:LINK-ListenAnzeigen-03.VAL"), + ], + "ListenAnzeigen-04": [ + PV("AGEOP-SHIFTTOOL:LINK-ListenAnzeigen-04.VAL"), + ], + "PiquetWoche": [ + PV("AGEOP-SHIFTTOOL:LINK-PiquetWoche.VAL"), + ], + "OffeneAuftraege": [ + PV("AGEOP-SHIFTTOOL:LINK-OffeneAuftraege.VAL"), + ], + } + + minisave = [ + PV("AGEBD-PARAMS:LIFETIME.VAL"), + PV("AGEBD-DBPM3CURR:CURRENT-AVG.VAL"), + PV("AGEBD-PARAMS:INJECTION-RATE.VAL"), + PV("AGEBD-DBPM3CURR:ACCUMULATED-BEAM-DOSE.VAL"), + PV("AGEOP-CPCL-TRANSMISSION:ICT-BRTL_2_BPM-RING.VAL"), + PV("ARSGE-CECL-FOFB1:X-RMS-UM.VAL"), + PV("ARSGE-CECL-FOFB1:Y-RMS-UM.VAL"), + PV("X01DD-FE-ABS1:STATUS.VAL"), + PV("X08DB-FE-ABS1:STATUS.VAL"), + PV("X01DA-FE-ABS1:STATUS.VAL"), + PV("X01DA-OP-PSYS:SH-A-OPEN.VAL"), + PV("X02DA-FE-ABS1:STATUS.VAL"), + PV("X02DA-OP-PSYS:SH-A-OPEN.VAL"), + PV("X06DA-FE-ABS1:STATUS.VAL"), + PV("X06DA-OP-PSYS:SH-A-OPEN.VAL"), + PV("X07DA-FE-ABS1:STATUS.VAL"), + PV("X07D-OP-PSYS:SH-A-OPEN.VAL"), + PV("X07DB-FE-ABS1:STATUS.VAL"), + PV("X07D-OP-PSYS:SH-B-OPEN.VAL"), + PV("X10DA-FE-ABS1:STATUS.VAL"), + PV("X10DA-OP-PSYS:SH-A-OPEN.VAL"), + PV("X04SA-UIND:GAP-READ.VAL"), + PV("X04SA-UIND:SCTRL.VAL"), + PV("X06SA-UIND:GAP-READ.VAL"), + PV("X06SA-UIND:SCTRL.VAL"), + PV("X09LB-UIND:GAP-READ.VAL"), + PV("X09LB-UIND:SCTRL.VAL"), + PV("X10SA-UIND:GAP-READ.VAL"), + PV("X10SA-UIND:SCTRL.VAL"), + PV("X12SA-UIND:GAP-READ.VAL"), + PV("X12SA-UIND:SCTRL.VAL"), + ] + + NShiftTasks = PV("AGEOP-SHIFTTOOL:CNT-TODO.VAL") def __init__(self, service_name: str, pv_factory=PV): 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( - "AGEBD-SHIFTTOOL:BO" - ) # TODO: is this a good PV to use? - - ## 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( - "AGEBD-SHIFTTOOL:CALLBACK", auto_monitor=dbr.DBE_VALUE - ) # TODO: ok? + self.CallbackPV = PV("AGEOP-SHIFTTOOL:REQ-CALC.VAL") class Service(BaseService[PVs]): @@ -56,38 +172,98 @@ class Service(BaseService[PVs]): ): super().__init__(name, pvs, version, sleep_interval) - # TODO: What is this for? - self.path = "sls/bd/bin" + # FACILITY = SLS + self.logbook = elog.open( + "https://elog-gfa.psi.ch/SLS/", + user="robot", + password="robot", + use_ssl=True, + ) + self.path = "/sls/op/data/applications/minisave/" + + def minisave(self): + + # table overview of current machine and beamline parameters + + return def update(self): - # when callback is fired, this code will be executed + pvs = self.pvs + if self.CallbackFired: - ####################################################################################### - ####################################################################################### - # here goes all the code that your service - # is supposed to be running when the callback is fired - time.sleep(1) - ####################################################################################### - ####################################################################################### + self.req_UpdateTeam = pvs.req_UpdateTeam.get() + self.req_MakeElog = pvs.req_MakeElog.get() + print(self.req_UpdateTeam, self.req_MakeElog) + + # if shift crew update request detected update current shift crew + if self.req_UpdateTeam or self.req_MakeElog: + pvs.req_UpdateTeam.put(0) + print("Team aktualisieren") + + self.logtxt = [] + self.logtxt.append("Eingetragene Mannschaft:") + self.date = datetime.now() + self.hour = int(self.date.strftime("%H")) + + # Format the date + self.day_abbr = self.date.strftime( + "%a" + ) # Abbreviated weekday (e.g., "Mi" for Mittwoch) + self.date_str = self.date.strftime( + "%d-%b-%Y" + ) # Day-Month-Year (e.g., "17-Jun-2026") + self.week_num = self.date.isocalendar()[1] # ISO calendar week number (e.g., 25) + self.timestamp = f"{self.day_abbr} {self.date_str} KW{self.week_num}" + + if self.hour > 4 and self.hour < 8: + self.shift = "Früh-Schicht" + elif self.hour > 12 and self.hour < 16: + self.shift = "Spät-Schicht" + elif self.hour > 20: + self.shift = "Nacht-Schicht" + else: + self.shift = "Verschlafen" + + self.shift += ", " + self.timestamp + pvs.shift.put(self.shift) + + self.operators = [] + for op in pvs.operators: + op = op.get(as_string=True) + if len(op) > 1: + self.operators.append(op) + + print(self.operators) + self.crew = ", ".join(self.operators) + pvs.crew.put(self.crew) + self.logtxt.append(self.crew) + print(self.crew) + + # if shift entry request detected and current shift entry not yet made, make it + if self.req_MakeElog: + pvs.req_MakeElog.put(0) + print("Schicht eintragen") + + # Write to elog + self.logtxt = "".join(self.logtxt) + + self.dict_of_attributes = { + "Wann": time(), + "Autor": self.crew, + "Eintrag": "Schicht-Übergabe", + "Titel": self.shift, + "Encoding": "html", + } + + print(" ...elog entry prepared...") + + self.msg_id = self.logbook.post( + self.logtxt, + attributes=self.dict_of_attributes, + suppress_email_notification=True, + attribute_as_param="value", + ) + print(" ...elog entry submitted...") # 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 - my_pv1_random_val = str(time.clock_gettime_ns(0))[-1] - self.pvs.my_pv1.put(my_pv1_random_val) - # self.pvs.my_pv2.put("MY_PV2 value") - - # get some pvs/vals - self.val1 = self.pvs.my_pv1.get() - self.val2 = self.pvs.my_pv2.get() - - # maybe sleep to limit the update loop execution rate - time.sleep(1) - ####################################################################################### - #######################################################################################