commit c88fa8bdcccb04198f765f492f64668698ab3295 Author: smathis Date: Tue Jul 28 14:07:13 2026 +0200 Initial commit of Beamline_elements frontend diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..577a65d --- /dev/null +++ b/.clang-format @@ -0,0 +1,246 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "(Test)?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +--- + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..44436f1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.0) +project(Beamline_elements_scfe) + + + +# Check if the required environment variables MIDASSYS and EPICSSYS are available +if (NOT DEFINED ENV{MIDASSYS}) + message(SEND_ERROR "MIDASSYS environment variable not defined.") +endif() +if (NOT DEFINED ENV{EPICSSYS}) + message(SEND_ERROR "EPICSSYS environment variable not defined.") +endif() + +set(CMAKE_CXX_STANDARD 11) +set(MIDASSYS $ENV{MIDASSYS}) +set(EPICSSYS $ENV{EPICSSYS}) + +# Select the correct EPICS library depending on the OS (currently Linus or Darwin are available) +if (${CMAKE_SYSTEM_NAME} MATCHES Linux) + link_directories(${EPICSSYS}/lib/linux-x86_64) + set(LIBS ${LIBS} -lpthread -lutil -lrt -lbsd -ldl) +endif() +if (${CMAKE_SYSTEM_NAME} MATCHES Darwin) + link_directories(${EPICSSYS}/lib/darwin-aarch64) + set(LIBS ${LIBS} -lutil -lca) +endif() + +add_executable(Beamline_elements_scfe + frontend/Beamline_elements_scfe.cxx +) + +target_compile_options(Beamline_elements_scfe PRIVATE -g) + +target_include_directories(Beamline_elements_scfe + PRIVATE + ${MIDASSYS}/include + ${EPICSSYS}/include + ${EPICSSYS}/include/os/Linux + ${EPICSSYS}/include/os/Darwin + ${EPICSSYS}/include/compiler/gcc + ${EPICSSYS}/include/compiler/clang + ${CMAKE_CURRENT_SOURCE_DIR}/mxml +) + +target_link_libraries(Beamline_elements_scfe + PRIVATE + ${EPICSSYS}/lib/$ENV{EPICS_HOST_ARCH}/libca.so + $ENV{MIDASSYS}/lib/libmidas.a + $ENV{MIDASSYS}/lib/libmfe.a + $ENV{MIDASSYS}/lib/libmidas-mfed.a + ${LIBS} +) + +# This copies the html configuration paths to the Midas installation +install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/help.html + ${CMAKE_CURRENT_LIST_DIR}/msp_config.html + ${CMAKE_CURRENT_LIST_DIR}/msetpoint.html + DESTINATION ${CMAKE_INSTALL_PREFIX}/msetpoint +) + +install( + TARGETS + Beamline_elements_scfe + RUNTIME DESTINATION bin +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5c8ae1 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Beamline elements frontend + +This repository contains the "Beamline_elements" frontend, which is used to +control beamline devices such as magnets, slits or beamblockers of indivual +instruments from MIDAS. + +This file is almost a verbatim copy of + from +core MIDAS. The only reason this repository exists is that "epics_fe.cxx" is +written as a C-style frontend, where the equipment and frontend is hardcoded in +the binary. Hence, this copy changes "EPICS Frontend" and "EPICS" to +"Beamline_elements" to match the agreed upon naming convention. + +## Requirements + +- Environment variable `MIDASSYS` which is a path to the MIDAS shared libraries +and headers. The libraries must be in `$MIDASSYS/lib`, the headers in +`$MIDASSYS/include`. +- Environment variable `EPICSSYS` which is a path to the EPICS shared libraries +and headers. The libraries must be in `$EPICSSYS/lib`, the headers in +`$EPICSSYS/include`. + +## Build + +Clone this repository, enter the cloned directory, and then build via CMake. + +```bash +cmake -S "$(pwd)" -B +cmake --build --clean-first -- -j8 +``` + +## Run + +```bash +/Beamline_elements_scfe +``` diff --git a/frontend/Beamline_elements_scfe.cxx b/frontend/Beamline_elements_scfe.cxx new file mode 100644 index 0000000..ce0b3be --- /dev/null +++ b/frontend/Beamline_elements_scfe.cxx @@ -0,0 +1,554 @@ +/********************************************************************\ + + Name: epics_fe.c + Created by: Stefan Ritt + + Contents: General EPICS frontend used for the MSetPoint system + +\********************************************************************/ + +#include + +#include +#include "cadef.h" +#include "dbDefs.h" +#include "epicsTime.h" + +#include "midas.h" +#include "mfe.h" +#include "odbxx.h" + +/*-- Globals -------------------------------------------------------*/ + +// timeout in seconds for caget operations +#define CAGET_TIMEOUT 30.0 + +// device types, must match msp_config.html:dt_string[] +#define DT_MAGNET 1 +#define DT_BEAMBLOCKER 2 +#define DT_PSA 3 +#define DT_SEPARATOR 4 +#define DT_SLIT 5 +#define DT_VALUE 6 + +/* The frontend name (client name) as seen by other MIDAS clients */ +const char *frontend_name = "Beamline_elements"; +/* The frontend file name, don't change it */ +const char *frontend_file_name = __FILE__; + +/*-- Equipment list ------------------------------------------------*/ + +BOOL equipment_common_overwrite = TRUE; + +int epics_read(char *pevent, int off); +int epics_loop(void); +int epics_init(void); +int epics_get_demand(int channel); +int epics_get_measured(int channel); + +void demandCallback(midas::odb &o); +void commandCallback(midas::odb &o); + +EQUIPMENT equipment[] = { + + {"Beamline_elements", /* equipment name */ + {21, 0, /* event ID, trigger mask */ + "SYSTEM", /* event buffer */ + EQ_PERIODIC, /* equipment type */ + 0, /* event source */ + "MIDAS", /* format */ + TRUE, /* enabled */ + RO_ALWAYS, + 60000, /* read event every 60 sec */ + 0, /* readout pause */ + 0, /* number of sub events */ + 10, /* log history every 10 seconds */ + "", "", ""} , + epics_read /* readout routine */ + }, + + {""} +}; + +struct { + int length = 0; + midas::odb settings; + midas::odb variables; + chid *demand = nullptr; + chid *measured = nullptr; + chid *status = nullptr; + chid *command = nullptr; + float *demandCache = nullptr; + unsigned int updateInterval; + bool allowWriteAccess; +} beamline; + +/*-- Error dispatcher causing communiction alarm -------------------*/ + +void epics_fe_error(const char *error) +{ + cm_msg(MERROR, "epics_fe_error", "%s", error); +} + +/*-- Readout routine -----------------------------------------------*/ + +int epics_read(char *pevent, int off) +{ + float *pdata; + + // init bank structure + bk_init(pevent); + + // create a bank with demand values + bk_create(pevent, "DMND", TID_FLOAT, (void **)&pdata); + for (int i = 0; i < beamline.length; i++) + *pdata++ = beamline.variables["Demand"][i]; + bk_close(pevent, pdata); + + // create a bank with measured values + bk_create(pevent, "MSRD", TID_FLOAT, (void **)&pdata); + for (int i = 0; i < beamline.length; i++) + *pdata++ = beamline.variables["Measured"][i]; + bk_close(pevent, pdata); + + return bk_size(pevent); +} + +/*-- Frontend Init -------------------------------------------------*/ + +int frontend_init() +{ + /* set error dispatcher for alarm functionality */ + mfe_set_error(epics_fe_error); + + // check for Beamline_elements gateway in ODB + if (!midas::odb::exists("/Equipment/Beamline_elements/Settings/EPICS gateway")) { + std::cout << "Missing \"Equipment/Beamline_elements/Settings/EPICS gateway\" in ODB." << std::endl; + std::cout << "Please load beamline configuration file into ODB." << std::endl; + return FE_ERR_ODB; + } + + // load EPICS settings from ODB + beamline.settings.connect("/Equipment/Beamline_elements/Settings"); + beamline.length = beamline.settings["Names"].size(); + beamline.variables.connect("/Equipment/Beamline_elements/Variables"); + + beamline.updateInterval = beamline.settings["Update interval"]; + beamline.allowWriteAccess = beamline.settings["Allow write access"]; + + // setup environment + std::string gw = beamline.settings["EPICS Gateway"]; + setenv("EPICS_CA_ADDR_LIST", gw.c_str(), 1); + setenv("EPICS_CA_AUTO_ADDR_LIST", "NO", 1); + + // create or correct "Variables" in ODB + if (!midas::odb::exists("/Equipment/Beamline_elements/Variables/Demand")) + beamline.variables["Demand"] = std::vector(beamline.length); + else + beamline.variables["Demand"].resize(beamline.length); + + if (!midas::odb::exists("/Equipment/Beamline_elements/Variables/Measured")) + beamline.variables["Measured"] = std::vector(beamline.length); + else + beamline.variables["Measured"].resize(beamline.length); + + std::string startCommand(__FILE__); + + // set start command in ODB if not already set + if (!midas::odb::exists("/Programs/Beamline_elements/Start command")) { + midas::odb efe("/Programs/Beamline_elements"); + efe["Start command"].set_string_size(startCommand, 256); + } else { + midas::odb efe("/Programs/Beamline_elements"); + if (efe["Start command"] == std::string("")) + efe["Start command"].set_string_size(startCommand, 256); + } + + install_frontend_loop(epics_loop); + + return epics_init(); +} + +/*------------------------------------------------------------------*/ + +int epics_init(void) +{ + int status = FE_SUCCESS; + + /* initialize driver */ + status = ca_task_initialize(); + if (!(status & CA_M_SUCCESS)) { + cm_msg(MERROR, "epics_init", "Unable to initialize Beamline_elements EPICS driver"); + return FE_ERR_HW; + } + + beamline.demand = (chid *) calloc(beamline.length, sizeof(chid)); + beamline.measured = (chid *) calloc(beamline.length, sizeof(chid)); + beamline.status = (chid *) calloc(beamline.length, sizeof(chid)); + beamline.command = (chid *) calloc(beamline.length, sizeof(chid)); + beamline.demandCache = (float *) calloc(beamline.length, sizeof(float)); + + for (int i = 0; i < beamline.length; i++) { + + printf("Channel %d\r", i); + fflush(stdout); + + std::string demand = beamline.settings["CA Demand"][i]; + if (demand != std::string("")) { + std::string name = beamline.settings["CA Name"][i]; + name += demand; + status = ca_create_channel(name.c_str(), nullptr, nullptr, 0, &(beamline.demand[i])); + SEVCHK(status, "ca_create_channel"); + if (ca_pend_io(5.0) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_init", "Cannot connect to EPICS channel %s (channel %d)", name.c_str(), i); + status = FE_ERR_HW; + break; + } + } + + std::string measured = beamline.settings["CA Measured"][i]; + if (measured != std::string("")) { + std::string name = beamline.settings["CA Name"][i]; + name += measured; + status = ca_create_channel(name.c_str(), nullptr, nullptr, 0, &(beamline.measured[i])); + SEVCHK(status, "ca_create_channel"); + if (ca_pend_io(5.0) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_init", "Cannot connect to EPICS channel %s (channel %d)", name.c_str(), i); + status = FE_ERR_HW; + break; + } + } + + std::string stat = beamline.settings["CA Status"][i]; + if (stat != std::string("")) { + std::string name = beamline.settings["CA Name"][i]; + name += stat; + status = ca_create_channel(name.c_str(), nullptr, nullptr, 0, &(beamline.status[i])); + SEVCHK(status, "ca_create_channel"); + if (ca_pend_io(5.0) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_init", "Cannot connect to EPICS channel %s (channel %d)", name.c_str(), i); + status = FE_ERR_HW; + break; + } + } + + std::string command = beamline.settings["CA Command"][i]; + if (command != std::string("")) { + std::string name = beamline.settings["CA Name"][i]; + if ((int)beamline.settings["Device type"][i] == DT_SEPARATOR) + name = name.substr(0, name.size() - 1); // Strip 'N' from SEP41VHVN + name += command; + status = ca_create_channel(name.c_str(), nullptr, nullptr, 0, &(beamline.command[i])); + SEVCHK(status, "ca_create_channel"); + if (ca_pend_io(5.0) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_init", "Cannot connect to EPICS channel %s (channel %d)", name.c_str(), i); + status = FE_ERR_HW; + break; + } + } + } + + // read initial demand values + for (int i = 0; i < beamline.length; i++) { + beamline.demandCache[i] = (float) ss_nan(); + epics_get_demand(i); + } + + // install callback for demand changes + beamline.variables["Demand"].watch(demandCallback); + + // install callback for direct commands + beamline.settings["Command"].watch(commandCallback); + + printf("\n"); + + return status; +} + +/*------------------------------------------------------------------*/ + +int epics_get_demand(int channel) +{ + int status; + char str[80]; + float d; + + // Skip channels which do not support demand values + if (beamline.demand[channel] == nullptr) + return FE_SUCCESS; + + std::string name = beamline.settings["Names"][channel]; + + if ((int)beamline.settings["Device type"][channel] == DT_BEAMBLOCKER) { + status = ca_get(DBR_STRING, beamline.demand[channel], str); + SEVCHK(status, "ca_get"); + if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_get_demand", "Timeout on EPICS channel %s", + name.c_str()); + return FE_ERR_HW; + } + d = (str[0] == 'O') ? 1 : 0; + } else { + status = ca_get(DBR_FLOAT, beamline.demand[channel], &d); + SEVCHK(status, "ca_get"); + if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_get_demand", "Timeout on EPICS channel %s", + name.c_str()); + return FE_ERR_HW; + } + } + + // set demand in ODB only if it differs (avoid loops via hotlink) + if (d != beamline.demandCache[channel]) { + beamline.variables["Demand"][channel] = d; + beamline.demandCache[channel] = d; + } + + return FE_SUCCESS; +} + +/*------------------------------------------------------------------*/ + +int epics_get_measured(int channel) +{ + int status; + char str[80]; + + // Skip write-only channels + if (beamline.measured[channel] == nullptr) + return FE_SUCCESS; + + std::string name = beamline.settings["Names"][channel]; + + if ((int)beamline.settings["Device type"][channel] == DT_BEAMBLOCKER || + (int)beamline.settings["Device type"][channel] == DT_PSA) { + + int d; + float f; + status = ca_get(DBR_LONG, beamline.measured[channel], &d); + SEVCHK(status, "ca_get"); + if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_get_measured", "Timeout on EPICS channel %s", + name.c_str()); + return FE_ERR_HW; + } + + if ((int)beamline.settings["Device type"][channel] == DT_BEAMBLOCKER) { + // bit0: open, bit1: closed, bit2: psa ok + if ((d & 3) == 0) + f = 0.5; + else if (d & 1) + f = 1; + else if (d & 2) + f = 0; + else + f = (float)ss_nan(); + } else { + // bit2: PSA Ok + if (d & 4) + f = 1; + else + f = 0; + } + + beamline.variables["Measured"][channel] = f; + + } else { + + float f; + status = ca_get(DBR_FLOAT, beamline.measured[channel], &f); + SEVCHK(status, "ca_get"); + if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_get_measured", "Timeout on EPICS channel %s", + name.c_str()); + return FE_ERR_HW; + } + beamline.variables["Measured"][channel] = f; + } + + return FE_SUCCESS; +} + +/*------------------------------------------------------------------*/ + +int epics_get_status(int channel) +{ + int status; + char s[80]; + + // Skip channels which do not support status readback + if (beamline.status[channel] == nullptr) + return FE_SUCCESS; + + status = ca_get(DBR_STRING, beamline.status[channel], s); + SEVCHK(status, "ca_get"); + std::string name = beamline.settings["Names"][channel]; + if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT) { + cm_msg(MERROR, "epics_get_status", "Timeout on EPICS channel %s", + name.c_str()); + return FE_ERR_HW; + } + + // translate to Englisch + std::string str(s); + if (str == "Eingeschaltet") + str = "On"; + else if (str == "Ausgeschaltet") + str = "Off"; + else if (str.compare(1, str.size()-1, "berlast") == 0) + str = "Overload"; + + beamline.settings["Status"][channel].set_string_size(str, 64); + + return FE_SUCCESS; +} + +/*------------------------------------------------------------------*/ + +void epics_set_demand(int channel, float value) +{ + int status, channelType; + char s[80]; + + // skip readonly channels + if (beamline.demand[channel] == nullptr) + return; + + channelType = beamline.settings["Device type"][channel]; + std::string channelName = beamline.settings["Names"][channel]; + + if (channelType == DT_BEAMBLOCKER) + status = ca_put(DBR_STRING, beamline.demand[channel], value == 1 ? "OPEN" : "CLOSE"); + + else if (channelType == DT_SLIT) { + status = ca_put(DBR_FLOAT, beamline.demand[channel], &value); + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_set_demand", "Cannot write to EPICS channel %s", channelName.c_str()); + + if (beamline.command[channel] == nullptr) { + cm_msg(MERROR, "epics_set_damand", "No \"CA Command\" defined for EPICS slit channel %s", channelName.c_str()); + return; + } + status = ca_put(DBR_STRING, beamline.command[channel], "MOVE_TO_SOL"); + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_set_demand", "Cannot write to EPICS channel %s", channelName.c_str()); + } + + else if (channelType == DT_SEPARATOR) { + status = ca_put(DBR_FLOAT, beamline.demand[channel], &value); + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_set_demand", "Cannot write to EPICS channel %s", channelName.c_str()); + + if (beamline.command[channel] == nullptr) { + cm_msg(MERROR, "epics_set_damand", "No \"CA Command\" defined for EPICS separator channel %s", channelName.c_str()); + return; + } + short int d = 3; // HVSET command + status = ca_put(DBR_SHORT, beamline.command[channel], &d); + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_set_demand", "Cannot write to EPICS channel %s", channelName.c_str()); + } else + status = ca_put(DBR_FLOAT, beamline.demand[channel], &value); + + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_set_demand", "Cannot write to EPICS channel \"%s\"", channelName.c_str()); +} + +/*------------------------------------------------------------------*/ + +void epics_send_command(int channel, std::string cmd) +{ + int status, channelType; + + /* skip readonly channels */ + if (beamline.command[channel] == nullptr) + return; + + channelType = beamline.settings["Device type"][channel]; + std::string channelName = beamline.settings["Names"][channel]; + + status = ca_put(DBR_STRING, beamline.command[channel], cmd.c_str()); + if (status != CA_M_SUCCESS) + cm_msg(MERROR, "epics_send_command", "Cannot send command \"%s\" to EPICS channel \"%s\"", + cmd.c_str(), channelName.c_str()); +} + +/*------------------------------------------------------------------*/ + +void demandCallback(midas::odb &o) { + std::vector newDemand = o; + + // check write flag + if (!beamline.allowWriteAccess) + return; + + for (int i = 0; i < beamline.length; i++) { + if (newDemand[i] != beamline.demandCache[i]) { + epics_set_demand(i, newDemand[i]); + // printf("Set %d to %f\n", i, newDemand[i]); + beamline.demandCache[i] = newDemand[i]; + } + } +} + +/*------------------------------------------------------------------*/ + +void commandCallback(midas::odb &o) { + std::vector cmd = o; + + // check write flag + if (!beamline.allowWriteAccess) + return; + + for (int i = 0; i < beamline.length; i++) { + if (!cmd[i].empty()) { + epics_send_command(i, cmd[i]); +// printf("Send command \"%s\" to \"%s\"\n", cmd[i].c_str(), +// beamline.settings["Names"][i].s().c_str()); + o[i] = ""; // delete command + } + } +} + +/*------------------------------------------------------------------*/ + +int epics_loop(void) +{ + static DWORD last_time_measured = 0; + static DWORD last_time_status = 0; + static DWORD last_time_demand = 0; + + // read values once per second + if (ss_millitime() - last_time_measured > beamline.updateInterval) { + for (int i = 0; i < beamline.length; i++) + epics_get_measured(i); + last_time_measured = ss_millitime(); + } + + // read status once every ten seconds + if (ss_time() >= last_time_status + 10) { + for (int i = 0; i < beamline.length; i++) + epics_get_status(i); + last_time_status = ss_time(); + } + + if (beamline.allowWriteAccess) { + // read demand values once every five minute (could have been changed by SetPoint) + if (ss_time() >= last_time_demand + 10 * 60 * 5) { + for (int i = 0; i < beamline.length; i++) + epics_get_demand(i); + last_time_demand = ss_time(); + } + } else { + // read demand values once every ten seconds (could have been changed by other MSetPoint) + if (ss_time() >= last_time_demand + 10) { + for (int i = 0; i < beamline.length; i++) + epics_get_demand(i); + last_time_demand = ss_time(); + } + + } + + ss_sleep(10); // don't eat all CPU + return CM_SUCCESS; +} \ No newline at end of file