diff --git a/.gitignore b/.gitignore index 6986c74..309f4e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .vscode/ CMakeCache.txt CMakeFiles -Makefile cmake_install.cmake midas.log ePowerSwitchFront build/ +Makefile \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d543d87..4a2301f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.03) -project(ePowerSwitchFront VERSION 1.0) +project(ePowerSwitchFrontend VERSION 1.0) add_subdirectory(submodule/mepicsca) add_compile_options( -Wall -Wformat=2 - -O3 + -g -Wno-format-nonliteral -Wno-strict-aliasing -Wuninitialized @@ -44,20 +44,33 @@ set(LIBS find_package(Midas REQUIRED) -add_executable(ePowerSwitchFront - src/ePowerSwitchFront.cpp +################################################################################ +## Device Library +################################################################################ + +add_library( + power_switch + src/device/power_switch.cpp + src/utils/outlet.cpp ) set_property( TARGET - ePowerSwitchFront + power_switch PROPERTY CXX_STANDARD 17 ) +target_link_libraries( + power_switch + PRIVATE + midas::mfe + m_epics_ca + ${LIBS} +) target_include_directories( - ePowerSwitchFront + power_switch PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE @@ -68,15 +81,46 @@ target_include_directories( ${EPICSSYS}/include/compiler/clang ) -target_link_libraries(ePowerSwitchFront + + +################################################################################ +## Frontend +################################################################################ + +add_executable(power_switch_scfe + src/frontend/power_switch_scfe.cpp +) + +set_property( + TARGET + power_switch_scfe + PROPERTY + CXX_STANDARD 17 +) + + +target_include_directories( + power_switch_scfe + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE + ${EPICSSYS}/include + ${EPICSSYS}/include/os/Linux + ${EPICSSYS}/include/os/Darwin + ${EPICSSYS}/include/compiler/gcc + ${EPICSSYS}/include/compiler/clang +) + +target_link_libraries( + power_switch_scfe + PRIVATE + power_switch midas::mfe m_epics_ca ${LIBS} ) - install( TARGETS - ePowerSwitchFront + power_switch_scfe RUNTIME DESTINATION bin ) diff --git a/include/ePowerSwitchConfig.h b/include/ePowerSwitchConfig.h deleted file mode 100644 index 933e254..0000000 --- a/include/ePowerSwitchConfig.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef EPOWERSWITCH_CONFIG_H -#define EPOWERSWITCH_CONFIG_H - -#define EPOWERSWITCH_SOCKET_SET_PREFIX "ePowerSwitch_set_outlet_" -#define EPOWERSWITCH_SOCKET_GET_PREFIX "ePowerSwitch_get_outlet_" -#define EPOWERSWITCH_SOCKETNUMBER_INFO_PREFIX "ePowerSwitch_config_maxOutlet" - -#define DEFAULT_FRONTEND_NAME "ePowerSwitch" -#define DEFAULT_EQUIPMENT_NAME "powerswitch" - -#endif diff --git a/include/ePowerSwitchFront.h b/include/ePowerSwitchFront.h deleted file mode 100644 index f4298e6..0000000 --- a/include/ePowerSwitchFront.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef EPOWERSWITCH_FRONTEND_H -#define EPOWERSWITCH_FRONTEND_H - -#include "m_epics_ca.h" -#include "tmfe.h" -#include - -class ePowerSwitchEquipment : public TMFeEquipment { - - public: - /** - * @brief constructor - * - * @param equipmentName name show on Midas for the equipment - * @param equipmentFileName file name for automatically tagged message - */ - ePowerSwitchEquipment(std::string equipmentName, - const char *equipmentFileName); - - /** - * @brief called periodically by Midas - */ - void HandlePeriodic(); - - private: - std::vector *> outletSetRecords; - std::vector *> outletGetRecords; - mEpicsCa outletNumberRecord; - int numberOfOutlet; - - /** - * @brief refresh information about the given socket ID - * - * @param socketId which socket to refresh - */ - void refreshSocket(int socketId); - - /** - * @brief refresh information about all the available sockets - */ - void refreshAllSockets(); - - /** - * @brief detect and modify the current socket pool in case of change - */ - void updateSocketNumber(); -}; - -class ePowerSwitchFrontend : public TMFrontend { - public: - /** - * @brief constructor - * - * @param frontendName name shown on Midas for the frontend - * @param equipmentName name shown on Midas for the equipment - */ - ePowerSwitchFrontend(std::string frontendName, std::string equipmentName); -}; - -#endif \ No newline at end of file diff --git a/src/device/power_switch.cpp b/src/device/power_switch.cpp new file mode 100644 index 0000000..9fe129c --- /dev/null +++ b/src/device/power_switch.cpp @@ -0,0 +1,105 @@ +#include "power_switch.h" +#include "odbxx.h" +#include "power_switch_config.h" +#include "tmfe.h" +#include + +#include + +/** + * @brief Insert a integer value between two string + * @param str1 first string + * @param int value to insert + * @param str2 second string + * + * @return a new string instance + */ +std::string insert(const std::string *str1, int value, + const std::string *str2) { + return *str1 + std::to_string(value) + *str2; +} + +powerSwitch::powerSwitch(std::string equipmentName, + const char *equipmentFilename) + : TMFeEquipment(equipmentName.c_str(), equipmentFilename), + outletNumberRecord(mEpicsCa(EPOWERSWITCH_OUTLETNUMBER_INFO_PREFIX)) { + fEqConfReadOnlyWhenRunning = false; + + this->equipmentPath = std::string("/Equipment/") + equipmentName; +} + +void powerSwitch::HandlePeriodic() { + + updateOutletNumber(); + midas::odb o(this->equipmentPath); + + for (int i = 0; i < this->numberOfOutlet; i++) { + Outlet *outlet = &this->outlets.at(i); + + if (outlet->commandHasChange()) { + o[COMMAND_PATH.c_str()][i] = outlet->getCommand(); + } + o[READBACK_PATH.c_str()][i] = outlet->getState(); + } +} +TMFeResult powerSwitch::HandleInit(const std::vector &args) { + + midas::odb o = { + {SETTING_DIR.c_str(), {{EDITABLE_VARNAME.c_str(), COMMAND_VARNAME}}}}; + + o.connect(this->equipmentPath.c_str()); + + midas::odb to_watch(this->equipmentPath + SLASH + VARIABLES_DIR + SLASH + + COMMAND_VARNAME); + + to_watch.watch( + [&](midas::odb &arg) { this->sendCommand(arg.get_last_index()); }); + return TMFeOk(); +} +void powerSwitch::updateOutletNumber() { + int newNumberOfOutlet = 0; + + int returnCode = outletNumberRecord.get(&newNumberOfOutlet); + if (returnCode != CM_SUCCESS) { + fMfe->Msg(MERROR, __FUNCTION__, "Unable to read outlet record number"); + } + + if (this->numberOfOutlet < newNumberOfOutlet) + fMfe->Msg(MDEBUG, __FUNCTION__, + "Outlet number increasing, creating %d new outlet(s)", + newNumberOfOutlet - this->numberOfOutlet); + + for (int i = this->numberOfOutlet; i < newNumberOfOutlet; i++) { + std::string epicsSetRecordName; + { + static const std::string *str1 = &EPOWERSWITCH_OUTLET_SET_PREFIX; + static const std::string str2 = ""; + epicsSetRecordName = insert(str1, i + 1, &str2); + } + std::string epicsGetRecordName; + { + static const std::string *str1 = &EPOWERSWITCH_OUTLET_GET_PREFIX; + static const std::string str2 = ""; + epicsGetRecordName = insert(str1, i + 1, &str2); + } + this->outlets.emplace_back(i, epicsGetRecordName, epicsSetRecordName); + } + + if (this->numberOfOutlet > newNumberOfOutlet) + fMfe->Msg(MDEBUG, __FUNCTION__, + "Outlet number decreasing, destroying %d outlet(s)", + this->numberOfOutlet - newNumberOfOutlet); + + for (int i = newNumberOfOutlet; i < this->numberOfOutlet; i++) { + this->outlets.pop_back(); + } + + this->numberOfOutlet = newNumberOfOutlet; +} + +void powerSwitch::sendCommand(int index) { + midas::odb o(this->equipmentPath); + std::string command = o[COMMAND_PATH.c_str()][index]; + Outlet *outlet = &outlets.at(index); + outlet->setState(command); +} diff --git a/src/device/power_switch.h b/src/device/power_switch.h new file mode 100644 index 0000000..1acd9a4 --- /dev/null +++ b/src/device/power_switch.h @@ -0,0 +1,25 @@ +#ifndef POWER_SWITCH_H +#define POWER_SWITCH_H + +#include "../utils/outlet.h" +#include "tmfe.h" +#include + +class powerSwitch : public TMFeEquipment { + public: + powerSwitch(std::string equipmentName, const char *equipmentFilename); + void HandlePeriodic(); + TMFeResult HandleInit(const std::vector &args); + + private: + std::vector outlets; + mEpicsCa outletNumberRecord; + int numberOfOutlet; + std::string equipmentPath; + + void updateOutletNumber(); + + void sendCommand(int index); +}; + +#endif \ No newline at end of file diff --git a/src/device/power_switch_config.h b/src/device/power_switch_config.h new file mode 100644 index 0000000..4d9bfd5 --- /dev/null +++ b/src/device/power_switch_config.h @@ -0,0 +1,28 @@ +#ifndef POWER_SWITCH_CONFIG_H +#define POWER_SWITCH_CONFIG_H + +#include + +const std::string EPOWERSWITCH_OUTLETNUMBER_INFO_PREFIX = + "ePowerSwitch_config_maxOutlet"; + +const std::string VARIABLES_DIR = "Variables"; +const std::string SETTING_DIR = "Settings"; +const std::string COMMAND_VARNAME = "Command"; +const std::string READBACK_VARNAME = "Readback"; + +const std::string SLASH = "/"; + +const std::string EPOWERSWITCH_OUTLET_SET_PREFIX = "ePowerSwitch_set_outlet_"; +const std::string EPOWERSWITCH_OUTLET_GET_PREFIX = "ePowerSwitch_get_outlet_"; + +const std::string EDITABLE_VARNAME = std::string("Editable"); + +// ################################################## +// Paths names : do not modify +// ################################################## + +const std::string COMMAND_PATH = VARIABLES_DIR + SLASH + COMMAND_VARNAME; +const std::string READBACK_PATH = VARIABLES_DIR + SLASH + READBACK_VARNAME; + +#endif \ No newline at end of file diff --git a/src/ePowerSwitchFront.cpp b/src/ePowerSwitchFront.cpp deleted file mode 100644 index 79ca831..0000000 --- a/src/ePowerSwitchFront.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "../include/ePowerSwitchFront.h" -#include "../include/ePowerSwitchConfig.h" -#include "m_epics_ca.h" -#include "tmfe.h" -#include - -/** - * @brief local function: append an integer to the end of a string. - * @param str string prefix - * @param value integer to append - * @return "str" + integer - */ -std::string format(std::string str, int value) { - std::stringstream ss; - ss << str; - ss << value; - return ss.str(); -} - -int main(int argc, char *argv[]) { - - if (argv[0][0] == '.') { - TMFE::Instance()->Msg(MERROR, __FUNCTION__, - "Relative paths are strongly discouraged; " - "please use an absolute path."); - } - - std::string frontendName; - std::string equipmentName; - - if (argc == 1) { - frontendName = DEFAULT_FRONTEND_NAME; - TMFE::Instance()->Msg(MINFO, __FUNCTION__, - "No frontend name provided; %s will be used", - DEFAULT_FRONTEND_NAME); - equipmentName = DEFAULT_EQUIPMENT_NAME; - TMFE::Instance()->Msg(MINFO, __FUNCTION__, - "No equipment name provided; %s will be used", - DEFAULT_EQUIPMENT_NAME); - } - if (argc == 2) { - frontendName = std::string(argv[1]); - argv++; - argc--; - TMFE::Instance()->Msg(MINFO, __FUNCTION__, - "No equipment name provided; %s will be use", - DEFAULT_EQUIPMENT_NAME); - } else if (argc == 3) { - frontendName = std::string(argv[1]); - equipmentName = std::string(argv[2]); - argv += 2; - argc -= 2; - } - - auto front = new ePowerSwitchFrontend(frontendName, equipmentName); - front->FeMain(argc, argv); -} - -ePowerSwitchEquipment::ePowerSwitchEquipment(std::string equipmentName, - const char *equipmentFileName) - : TMFeEquipment(equipmentName.c_str(), equipmentFileName), - outletNumberRecord(mEpicsCa( - std::string_view(EPOWERSWITCH_SOCKETNUMBER_INFO_PREFIX))) { - - fEqConfReadConfigFromOdb = false; /// i don't know what this parameter does - fEqConfPeriodMilliSec = 1000; /// refresh rate of midas frontend - fEqConfLogHistory = - 60; /// enable history system, generate one event per minutes - fEqConfReadOnlyWhenRunning = false; /// allow to write values when running - fEqConfWriteEventsToOdb = true; /// i don't know - - this->numberOfOutlet = 0; -} - -void ePowerSwitchEquipment::HandlePeriodic() { - updateSocketNumber(); - refreshAllSockets(); -} - -void ePowerSwitchEquipment::refreshSocket(int socketId) { - std::string requestedVarname = - format("Socket ", socketId) + std::string(" requested"); - std::string currentVarname = - format("Socket ", socketId) + std::string(" current"); - - std::string midasRequestedSocketState = std::string("Off"); - MVOdbError ovbError; - int mepicscaReturnCode; - fOdbEqVariables->RS(requestedVarname.c_str(), &midasRequestedSocketState, - true, midasRequestedSocketState.length() + 1, - &ovbError); - - if (ovbError.fError) { - fMfe->Msg(MERROR, __FUNCTION__, ovbError.fErrorString); - exit(EXIT_FAILURE); - } - - std::string epicsCurrentSocketState; - mepicscaReturnCode = - this->outletGetRecords.at(socketId)->get(&epicsCurrentSocketState); - - if (mepicscaReturnCode != CM_SUCCESS) { - fMfe->Msg(MERROR, __FUNCTION__, - "Couldn't get the value, skipping refreshing socket %d. " - "Error code %d", - socketId, mepicscaReturnCode); - return; - } - - if (epicsCurrentSocketState != midasRequestedSocketState) { - mepicscaReturnCode = this->outletSetRecords.at(socketId)->put( - &midasRequestedSocketState); - - if (mepicscaReturnCode != CM_SUCCESS) { - fMfe->Msg(MERROR, __FUNCTION__, - "Couldn't put the value, skipping refreshing socket %d. " - "Error code %d", - socketId, mepicscaReturnCode); - return; - } - } - - fOdbEqVariables->WS(currentVarname.c_str(), epicsCurrentSocketState.c_str(), - epicsCurrentSocketState.length() + 1, &ovbError); - - if (ovbError.fError) { - fMfe->Msg(MERROR, __FUNCTION__, ovbError.fErrorString); - exit(EXIT_FAILURE); - } -} - -void ePowerSwitchEquipment::refreshAllSockets() { - for (int i = 0; i < this->numberOfOutlet; i++) { - refreshSocket(i); - } -} - -void ePowerSwitchEquipment::updateSocketNumber() { - int epicsOutletNumber = 0; - - if (!outletNumberRecord.connected()) { - fMfe->Msg(MERROR, __FUNCTION__, - "Couldn't connect to outlet number record."); - return; - } - int returnCode = outletNumberRecord.get(&epicsOutletNumber); - - if (returnCode < 0) { - fMfe->Msg(MERROR, __FUNCTION__, - "Couldn't get outlet number record. [ERROR_CODE : %d]\n " - "Skipping function execution", - returnCode); - return; - } - - if (this->numberOfOutlet < epicsOutletNumber) - fMfe->Msg(MDEBUG, __FUNCTION__, - "Socket number increasing, creating %d new socket(s)", - epicsOutletNumber - this->numberOfOutlet); - - for (int i = this->numberOfOutlet; i < epicsOutletNumber; i++) { - std::string epicsSetRecordName = - format(EPOWERSWITCH_SOCKET_SET_PREFIX, i); - - this->outletSetRecords.push_back( - new mEpicsCa(epicsSetRecordName.c_str())); - - std::string epicsGetRecordName = - format(EPOWERSWITCH_SOCKET_GET_PREFIX, i); - this->outletGetRecords.push_back( - new mEpicsCa(epicsGetRecordName.c_str())); - } - - if (this->numberOfOutlet > epicsOutletNumber) - fMfe->Msg(MDEBUG, __FUNCTION__, - "Socket number decreasing, destroying %d socket(s)", - this->numberOfOutlet - epicsOutletNumber); - - for (int i = epicsOutletNumber; i < this->numberOfOutlet; i++) { - mEpicsCa *setRecord = this->outletSetRecords.back(); - delete setRecord; - mEpicsCa *getRecord = this->outletGetRecords.back(); - delete getRecord; - - std::string requestedVarname = - format("Socket ", i) + std::string(" requested"); - std::string currentVarname = - format("Socket ", i) + std::string(" current"); - - MVOdbError ovbError; - - fOdbEqVariables->Delete(requestedVarname.c_str(), &ovbError); - if (ovbError.fError) - fMfe->Msg(MERROR, __FUNCTION__, ovbError.fErrorString); - - fOdbEqVariables->Delete(currentVarname.c_str(), &ovbError); - if (ovbError.fError) - fMfe->Msg(MERROR, __FUNCTION__, ovbError.fErrorString); - - this->outletSetRecords.pop_back(); - this->outletGetRecords.pop_back(); - } - - this->numberOfOutlet = epicsOutletNumber; -} - -ePowerSwitchFrontend::ePowerSwitchFrontend(std::string frontendName, - std::string equipmentName) { - FeSetName(frontendName.c_str()); - FeAddEquipment(new ePowerSwitchEquipment(equipmentName.c_str(), __FILE__)); -} \ No newline at end of file diff --git a/src/frontend/power_switch_scfe.cpp b/src/frontend/power_switch_scfe.cpp new file mode 100644 index 0000000..a65d454 --- /dev/null +++ b/src/frontend/power_switch_scfe.cpp @@ -0,0 +1,49 @@ +#include "power_switch_scfe.h" +#include "../device/power_switch.h" +#include "power_switch_scfe_config.h" +#include "tmfe.h" + +int main(int argc, char *argv[]) { + + if (argv[0][0] == '.') { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Relative paths are strongly discouraged; " + "please use an absolute path."); + } + + std::string frontendName; + std::string equipmentName; + + if (argc == 1) { + frontendName = DEFAULT_FRONTEND_NAME; + TMFE::Instance()->Msg(MINFO, __FUNCTION__, + "No frontend name provided; %s will be used", + DEFAULT_FRONTEND_NAME); + equipmentName = DEFAULT_EQUIPMENT_NAME; + TMFE::Instance()->Msg(MINFO, __FUNCTION__, + "No equipment name provided; %s will be used", + DEFAULT_EQUIPMENT_NAME); + } + if (argc == 2) { + frontendName = std::string(argv[1]); + argv++; + argc--; + TMFE::Instance()->Msg(MINFO, __FUNCTION__, + "No equipment name provided; %s will be use", + DEFAULT_EQUIPMENT_NAME); + } else if (argc == 3) { + frontendName = std::string(argv[1]); + equipmentName = std::string(argv[2]); + argv += 2; + argc -= 2; + } + + auto front = powerSwitchSCFE(frontendName, equipmentName); + front.FeMain(argc, argv); +} + +powerSwitchSCFE::powerSwitchSCFE(std::string frontendName, + std::string equipmentName) { + FeSetName(frontendName.c_str()); + FeAddEquipment(new powerSwitch(equipmentName.c_str(), __FILE__)); +} \ No newline at end of file diff --git a/src/frontend/power_switch_scfe.h b/src/frontend/power_switch_scfe.h new file mode 100644 index 0000000..61f1dd6 --- /dev/null +++ b/src/frontend/power_switch_scfe.h @@ -0,0 +1,17 @@ +#ifndef POWER_SWITCH_SCFE_H +#define POWER_SWITCH_SCFE_H + +#include "tmfe.h" + +class powerSwitchSCFE : public TMFrontend { + public: + /** + * @brief constructor + * + * @param frontendName name shown on Midas for the frontend + * @param equipmentName name shown on Midas for the equipment + */ + powerSwitchSCFE(std::string frontendName, std::string equipmentName); +}; + +#endif \ No newline at end of file diff --git a/src/frontend/power_switch_scfe_config.h b/src/frontend/power_switch_scfe_config.h new file mode 100644 index 0000000..b963946 --- /dev/null +++ b/src/frontend/power_switch_scfe_config.h @@ -0,0 +1,7 @@ +#ifndef POWER_SWITCH_SCFE_CONFIG_H +#define POWER_SWITCH_SCFE_CONFIG_H + +#define DEFAULT_FRONTEND_NAME "powerSwitch" +#define DEFAULT_EQUIPMENT_NAME "powerswitch" + +#endif \ No newline at end of file diff --git a/src/utils/outlet.cpp b/src/utils/outlet.cpp new file mode 100644 index 0000000..953cb7c --- /dev/null +++ b/src/utils/outlet.cpp @@ -0,0 +1,201 @@ +#include "outlet.h" +#include "m_epics_ca.h" +#include "odbxx.h" +#include "tmfe.h" +#include + +Outlet::Outlet(int index, std::string inputRecordName, + std::string outputRecordName) + : input(mEpicsCa(inputRecordName)), + output(mEpicsCa(outputRecordName)) { + this->index = index; + this->innerState = State::UNKNOW; + + this->isOutputConnected = true; + this->isInputConnected = true; + this->gracePeriod = 3; +} + +std::string Outlet::getState() { + this->updateState(); + return this->stateToString(this->innerState); +} + +std::string Outlet::getCommand() { + State command = this->_getCommand(); + this->lastCommand = command; + return this->stateToString(command); +} + +bool Outlet::commandHasChange() { + return this->lastCommand != this->_getCommand(); +} + +void Outlet::setState(State state) { + this->buffered = state; + this->updateState(); +} + +void Outlet::setState(std::string state) { + this->setState(this->stringToState(state)); +} + +void Outlet::turnOn() { + std::string msg = "On"; + int returnCode = this->output.put(&msg); + if (returnCode != CM_SUCCESS) + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Outlet %d : Error %d : unable to write into output record", + this->index, returnCode); +} + +void Outlet::turnOff() { + std::string msg = "Off"; + int returnCode = this->output.put(&msg); + if (returnCode != CM_SUCCESS) + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Outlet %d : Error %d : unable to write into output record", + this->index, returnCode); +} + +void Outlet::restart() { + std::string msg = "Restart"; + int returnCode = this->output.put(&msg); + if (returnCode != CM_SUCCESS) + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Outlet %d : Error %d : unable to write into output record", + this->index, returnCode); +} + +void Outlet::updateState() { + bool outputConnected = this->output.connected(); + bool inputConnected = this->input.connected(); + + if (!outputConnected && this->isOutputConnected) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Outlet %d : Output channel disconnected", + this->index); + } + + if (!inputConnected && this->isInputConnected) { + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Outlet %d : Input channel disconnected", + this->index); + } + + this->isOutputConnected = outputConnected; + this->isInputConnected = inputConnected; + + if (!outputConnected || !inputConnected) { + // Error message are above + return; + } + + // Adding a grace period to prevent reseting at start + if (this->gracePeriod > 0) { + this->gracePeriod--; + buffered.reset(); + } + + std::string value; + + int returnCode = this->input.get(&value); + if (returnCode != CM_SUCCESS) { + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Outlet %d : Error %d : unable to read from input record", + this->index, returnCode); + } + + this->innerState = stringToState(value); + + if (buffered.has_value()) { + State state = buffered.value(); + + switch (this->innerState) { + case State::ON: + if (state == State::OFF) + this->turnOff(); + if (state == State::RST) + this->restart(); + buffered.reset(); + break; + case State::OFF: + if (state == State::ON) + this->turnOn(); + if (state == State::RST) + this->restart(); + buffered.reset(); + break; + case State::RST: + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Outlet %d in restarting state, command " + "pending in a buffered state", + this->index); + break; + case State::UNKNOW: + TMFE::Instance()->Msg(MINFO, __FUNCTION__, + "Outlet %d : Unknow state", this->index); + if (state == State::ON) + this->turnOn(); + if (state == State::OFF) + this->turnOff(); + if (state == State::RST) + this->restart(); + buffered.reset(); + break; + + default: // This state doesn't exist + TMFE::Instance()->Msg(MERROR, __FUNCTION__, + "Outlet %d : FATAL ERROR : unvalide state", + this->index); + exit(EXIT_FAILURE); + break; + } + } +} + +Outlet::State Outlet::stringToState(std::string value) { + if (value == "ON" || value == "On") { + return State::ON; + } + if (value == "OFF" || value == "Off") { + return State::OFF; + } + if (value == "RST" || value == "Rst" || value == "Res" || value == "RES" || + value == "RESTART" || value == "Restart") { + return State::RST; + } + return State::UNKNOW; +} + +std::string Outlet::stateToString(State state) { + switch (state) { + case State::ON: + return std::string("On"); + break; + case State::OFF: + return std::string("Off"); + break; + case State::RST: + return std::string("Restart"); + break; + default: + return std::string("Unknow"); + break; + }; +} + +Outlet::State Outlet::_getCommand() { + std::string value; + int returnCode = this->output.get(&value); + if (returnCode != CM_SUCCESS) + TMFE::Instance()->Msg( + MERROR, __FUNCTION__, + "Outlet %d : Error %d : unable to read from output record", + this->index, returnCode); + return stringToState(value); +} diff --git a/src/utils/outlet.h b/src/utils/outlet.h new file mode 100644 index 0000000..021242b --- /dev/null +++ b/src/utils/outlet.h @@ -0,0 +1,64 @@ +#ifndef OUTLET_H +#define OUTLET_H + +#include "m_epics_ca.h" + +class Outlet { + public: + Outlet(int index, std::string inputRecordName, + std::string outputRecordName); + + enum class State { ON, OFF, RST, UNKNOW }; + + Outlet(Outlet &&other) noexcept = default; + + /** + * @brief get the current state of the device by reading it + * @return the current state + */ + std::string getState(); + + /** + * @brief get the value of the command stored in the command buffer + * @return the current command state + */ + std::string getCommand(); + /** + * @brief return true if the last value readed from this object if not equal + * to the current device state + */ + bool commandHasChange(); + /** + * @brief set to a new state the device + */ + void setState(State state); + /** + * @brief set to a new state the device. If string not recognize, will put + * UNKNOW to the outlet state + */ + void setState(std::string state); + + private: + mEpicsCa input; + mEpicsCa output; + + bool isInputConnected; + bool isOutputConnected; + + int gracePeriod; + + std::optional buffered; + State innerState; + int index; + + void turnOn(); + void turnOff(); + void restart(); + void updateState(); + Outlet::State stringToState(std::string value); + std::string stateToString(State state); + State _getCommand(); + State lastCommand; +}; + +#endif \ No newline at end of file diff --git a/submodule/mepicsca b/submodule/mepicsca index abc5781..b6aca41 160000 --- a/submodule/mepicsca +++ b/submodule/mepicsca @@ -1 +1 @@ -Subproject commit abc57815eb9992d489d30fd315f93ded52ebc5a0 +Subproject commit b6aca4174e2789cb1bf4a3f56b8a26446994d961