diff --git a/src/device/powerSwitch.cpp b/src/device/powerSwitch.cpp new file mode 100644 index 0000000..2ca08a4 --- /dev/null +++ b/src/device/powerSwitch.cpp @@ -0,0 +1,105 @@ +#include "powerSwitch.h" +#include "odbxx.h" +#include "powerSwitchConfig.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/powerSwitch.h b/src/device/powerSwitch.h new file mode 100644 index 0000000..1acd9a4 --- /dev/null +++ b/src/device/powerSwitch.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/powerSwitchConfig.h b/src/device/powerSwitchConfig.h new file mode 100644 index 0000000..4d9bfd5 --- /dev/null +++ b/src/device/powerSwitchConfig.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/utils/outlet.cpp b/src/utils/outlet.cpp new file mode 100644 index 0000000..841be75 --- /dev/null +++ b/src/utils/outlet.cpp @@ -0,0 +1,202 @@ +#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) { + printf("set a new states \n"); + 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; + } + + 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(); + printf("update state to \"%s\" sur l'outlet %d\n", value.c_str(), + this->index); + 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..bece194 --- /dev/null +++ b/src/utils/outlet.h @@ -0,0 +1,44 @@ +#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; + + std::string getState(); + std::string getCommand(); + bool commandHasChange(); + void setState(State 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