Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: ICE-Application/src/ConfigurationHandler/Controls/FailsafeControl.cpp
- Revision:
- 0:61364762ee0e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ICE-Application/src/ConfigurationHandler/Controls/FailsafeControl.cpp Tue Jan 24 19:05:33 2017 +0000
@@ -0,0 +1,534 @@
+/******************************************************************************
+ *
+ * File: FailsafeControl.cpp
+ * Desciption: ICE Failsafe Control Class implementation
+ *
+ *****************************************************************************/
+#include "FailsafeControl.h"
+#include "cJSON.h"
+#include "ICELog.h"
+#include "global.h"
+#include "ModbusMasterApi.h"
+#include "utilities.h"
+#include <string>
+#include <iostream>
+#include <iomanip>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <time.h>
+
+using namespace std;
+
+#ifdef MDOT_ICE
+extern mDot *GLOBAL_mdot;
+#endif
+
+// for debugging - this can be set via the console command:
+// "debug-fs 1 or debug-fs 0
+bool debugFailsafeControl = false;
+
+static void debug(const char *fmt, ...)
+{
+ if ( debugFailsafeControl ) {
+ va_list vargs;
+ va_start(vargs, fmt);
+ vfprintf(stdout, fmt, vargs);
+ va_end(vargs);
+ }
+}
+
+//
+// method: load
+// description: load a composite control
+//
+// @param none
+// @return none
+//
+bool FailsafeControl::load(std::string _controlFile)
+{
+ controlFile = _controlFile;
+
+
+ // read the data into a buffer
+ char dataBuf[MAX_FILE_SIZE];
+
+ bool rc = GLOBAL_mdot->readUserFile(controlFile.c_str(), (void *)dataBuf, sizeof(dataBuf));
+ if ( rc != true ) {
+ logError("%s: failed to read %s", __func__, controlFile.c_str());
+ // caller should destroy the object
+ return false;
+ }
+
+
+ if ( !validateControlData(dataBuf) ) {
+ logError("%s: failed to validate control data", __func__);
+ return false;
+ }
+
+ copyControlData(dataBuf);
+
+ ModbusValue val;
+ // validate the input & output
+ if ( ModbusMasterReadRegister(input, &val) == false ) {
+ logError("%s failed to find input %s", id.c_str(), input.c_str());
+ return false;
+ }
+ if ( ModbusMasterReadRegister(output, &val) == false ) {
+ logError("%s failed to find output %s", id.c_str(), output.c_str());
+ return false;
+ }
+
+ isVirtualOutput = Util_isVirtualOutput(output) ? true : false;
+ return true;
+}
+//
+// method: validateControlData
+// description: validate the JSON formatted strings for
+// expected tags
+//
+// @param[in] buf -> JSON formatted string
+// @param[out] none
+// @return true if valid; false otherwise
+//
+bool FailsafeControl::validateControlData(const char *buf)
+{
+ bool rc = true;
+ // parse the data
+ cJSON * root = cJSON_Parse(buf);
+
+ if ( !cJSON_HasObjectItem(root, "id") ||
+ !cJSON_HasObjectItem(root, "priority") ||
+ !cJSON_HasObjectItem(root, "input") ||
+ !cJSON_HasObjectItem(root, "output") ||
+ !cJSON_HasObjectItem(root, "hfsValue") ||
+ !cJSON_HasObjectItem(root, "hfsDutyCycle") ||
+ !cJSON_HasObjectItem(root, "hfsInterval") ||
+ !cJSON_HasObjectItem(root, "lfsValue") ||
+ !cJSON_HasObjectItem(root, "lfsDutyCycle") ||
+ !cJSON_HasObjectItem(root, "lfsInterval") ) {
+ logError("%s: control file is missing expected tags", __func__);
+ rc = false;
+ }
+
+ cJSON_Delete(root);
+ return rc;
+}
+
+//
+// method: copyControlData
+// description: copy data from JSON buffer to object
+//
+// @param[in] buf -> JSON formattted string
+// @param[out] none
+// @return none
+//
+void FailsafeControl::copyControlData(const char *buf)
+{
+ cJSON *root = cJSON_Parse(buf);
+
+ id = cJSON_GetObjectItem(root, "id")->valuestring;
+ priority = atoi(cJSON_GetObjectItem(root, "priority")->valuestring);
+ input = cJSON_GetObjectItem(root, "input")->valuestring;
+ output = cJSON_GetObjectItem(root, "output")->valuestring;
+
+ // high failsafe data
+ hfs_data.value = atof(cJSON_GetObjectItem(root, "hfsValue")->valuestring);
+ hfs_data.dutyCycle = atoi(cJSON_GetObjectItem(root, "hfsDutyCycle")->valuestring);
+ hfs_data.interval = atoi(cJSON_GetObjectItem(root, "hfsInterval")->valuestring);
+
+ // low failsafe data
+ lfs_data.value = atof(cJSON_GetObjectItem(root, "lfsValue")->valuestring);
+ lfs_data.dutyCycle = atoi(cJSON_GetObjectItem(root, "lfsDutyCycle")->valuestring);
+ lfs_data.interval = atoi(cJSON_GetObjectItem(root, "lfsInterval")->valuestring);
+
+ cJSON_Delete(root);
+}
+
+//
+// method: start
+// description: start the failsafe control
+//
+// @param none
+// @return none
+//
+void FailsafeControl::start(void)
+{
+ currentState = STATE_START;
+}
+
+//
+// method: update
+// description: update the faisafe control using the FSM
+//
+// @param none
+// @return none
+//
+FailsafeControlError_t FailsafeControl::update(void)
+{
+ FailsafeControlError_t rc = FAILSAFE_CONTROL_OK;
+
+ switch (this->currentState) {
+ case STATE_INIT:
+ // do nothing - control must be programatically started
+ break;
+ case STATE_START:
+ // control has programmatically started, do some checking
+ if ( this->aboveHighFailsafe() ) {
+ if ( hfs_data.dutyCycle) {
+ debug("\r%s: [START]->above hfs->[ON_HFS]\n", id.c_str());
+ // start high failsafe duty cycle
+ this->currentState = STATE_CONTROL_ON_HFS;
+ // turn the pump ON for the duty cycle, start the timer
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startHfsDutyTimer();
+ } else {
+ // no duty cycle specified, so go into the high-faisafe
+ // fixed-off state
+ debug("\r%s: [START]->above hfs && !dc->[OFF_HFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF_HFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ // no need to start the duty timer
+ }
+ } else if ( this->belowLowFailsafe() ) {
+ debug("\r%s: [START]->below lfs->[ON_LFS]\n", id.c_str());
+ // start low failsafe duty cycle
+ if ( lfs_data.dutyCycle ) {
+ this->currentState = STATE_CONTROL_ON_LFS;
+ // turn the pump ON for the duty cycle, start the timer
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startLfsDutyTimer();
+ } else {
+ // no duty cycle specified, so go into the low-failsafe
+ // fixed off-state
+ this->currentState = STATE_CONTROL_OFF_LFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ // no need to start the timer
+ }
+ } else {
+ debug("\r%s: [START]->no conditions->[OFF]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF;
+ }
+ break;
+ case STATE_CONTROL_OFF:
+ // control is acting normal, within bounds
+ if ( this->aboveHighFailsafe() ) {
+ if ( hfs_data.dutyCycle ) {
+ debug("\r%s: [OFF]->above hfs->[ON HFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_ON_HFS;
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startHfsDutyTimer();
+ } else {
+ debug("\r%s: [OFF]->above hfs && no dc->[OFF HFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF_HFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ }
+ } else if ( this->belowLowFailsafe() ) {
+ // restart the low failsafe duty cycle
+ debug("\r%s: [OFF]->below lfs->[ON_LFS]\n", id.c_str());
+ if ( lfs_data.dutyCycle ) {
+ this->currentState = STATE_CONTROL_ON_LFS;
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startLfsDutyTimer();
+ } else {
+ this->currentState = STATE_CONTROL_OFF_LFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ }
+ } else {
+ // do nothing
+ }
+ break;
+ case STATE_CONTROL_ON_LFS:
+ // control is in low-failsafe with duty cycle ON
+ if ( !this->belowLowFailsafe() ) {
+ // input has fallen below the failsafe, so turn off the control
+ debug("\r%s: [ON_LFS]->above lfs->[OFF]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF;
+ this->unregisterControl();
+ } else if ( this->dutyOnExpired() ) {
+ // duty ON has expired, so let's idle in the OFF-LFS state now
+ debug("\r%s: [ON_LFS]->duty on expired->[OFF LFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF_LFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ } else {
+ // do nothing
+ }
+ break;
+ case STATE_CONTROL_OFF_LFS:
+ // control is in low-failsafe with duty cycle OFF
+ if ( !this->belowLowFailsafe() ) {
+ // no longer in dangerous waters, so transition to OFF
+ debug("\r%s: [OFF_LFS]->above lfs->[OFF]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF;
+ this->unregisterControl();
+ } else if ( this->dutyOffExpired() && lfs_data.dutyCycle ) {
+ debug("\r%s: [OFF_LFS]->duty OFF exp->[ON_LFS]", id.c_str());
+ // turn the PUMP back on and restart the duty timer
+ this->currentState = STATE_CONTROL_ON_LFS;
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startLfsDutyTimer();
+
+ } else {
+ // do nothing
+ }
+ break;
+ case STATE_CONTROL_ON_HFS:
+ // control is in high-failsafe with duty cycle ON
+ if ( !this->aboveHighFailsafe() ) {
+ debug("\r%s: [ON_HFS]->below hfs->[OFF]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF;
+ this->unregisterControl();
+ } else if ( this->dutyOnExpired() ) {
+ debug("\r%s: [ON_HFS]->duty ON expired->[OFF_HFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF_HFS;
+ sendMailToOutput(ACTION_CONTROL_OFF);
+ } else {
+ // do nothing
+ }
+ break;
+ case STATE_CONTROL_OFF_HFS:
+ // control is in high-failsafe with cuty cycle OFF
+ if ( !this->aboveHighFailsafe() ) {
+ debug("\r%s: [OFF_HFS]->below hfs->[OFF]\n", id.c_str());
+ this->currentState = STATE_CONTROL_OFF;
+ this->unregisterControl();
+ } else if ( this->dutyOffExpired() && hfs_data.dutyCycle ) {
+ debug("\r%s: [OFF_HFS]->duty OFF expired->[ON_HFS]\n", id.c_str());
+ this->currentState = STATE_CONTROL_ON_HFS;
+ sendMailToOutput(ACTION_CONTROL_ON);
+ this->startHfsDutyTimer();
+ } else {
+ // do nothing
+ }
+ break;
+ default:
+ logError("%s: unknown state %d", __func__, this->currentState);
+ rc = FAILSAFE_CONTROL_UNK_STATE;
+ break;
+ }
+ return rc;
+}
+
+//
+// method: belowFailsafe
+// description: returns true if the input signal is below the
+// failsafe mark
+//
+// @param[in] none
+// @param[out] none
+// @return true if below; false otherwise
+//
+bool FailsafeControl::belowLowFailsafe(void)
+{
+ // read the modbus input
+ ModbusValue value;
+ ModbusMasterReadRegister(input, &value);
+ return ( value.value <= lfs_data.value );
+}
+
+//
+// method: aboveHighFailsafe
+// description: returns true if the input signal is above the
+// failsafe mark
+//
+// @param[in] none
+// @param[out] none
+// @return true if above; false otherwise
+//
+bool FailsafeControl::aboveHighFailsafe(void)
+{
+ // read the modbus input
+ ModbusValue value;
+ ModbusMasterReadRegister(input, &value);
+ return ( value.value >= hfs_data.value );
+}
+
+//
+// method: startHfsDutyTimer
+// description: start the high failsafe duty timer
+//
+// @param none
+// @return none
+//
+void FailsafeControl::startHfsDutyTimer(void)
+{
+ unsigned long currentTime = time(NULL);
+ unsigned long period = hfs_data.interval * 60;
+
+ duty_timer.offTime = currentTime + ((double)period * ((double)hfs_data.dutyCycle/100.0));
+ duty_timer.expirationTime = currentTime + period;
+
+ debug("\r%s:%s-> currentTime = %lu\n", __func__, id.c_str(), currentTime);
+ debug("\r%s:%s-> off Time = %lu\n", __func__, id.c_str(), duty_timer.offTime);
+ debug("\r%s:%s-> expiration = %lu\n", __func__, id.c_str(),duty_timer.expirationTime);
+}
+
+//
+// method: stopHfsDutyTimer
+// description: stop the high failsafe duty timer
+//
+// @param [in] none
+// @param[out] none
+// @return none
+//
+void FailsafeControl::stopHfsDutyTimer(void)
+{
+ debug("\r%sL%s-> invoked\n", __func__, id.c_str());
+ memset(&duty_timer, 0, sizeof(duty_timer));
+}
+
+//
+// method: startLfsDutyTimer
+// descrption: start the low failsafe duty-timer
+//
+// @param[in] none
+// @param[out] none
+// @return none
+//
+void FailsafeControl::startLfsDutyTimer(void)
+{
+ unsigned long currentTime = time(NULL);
+ unsigned long period = lfs_data.interval * 60;
+
+ duty_timer.offTime = currentTime + ((double)period * ((double)lfs_data.dutyCycle/100.0));
+ duty_timer.expirationTime = currentTime + period;
+
+ debug("\r%s:%s-> next off time = %lu\n", __func__, id.c_str(), duty_timer.offTime);
+ debug("\r%s:%s->expiration time = %lu\n", __func__, id.c_str(), duty_timer.expirationTime);
+}
+
+//
+// method: stopLfsDutyTimer
+// description: stop the low failsafe duty-timer
+//
+// @param none
+// @return none
+//
+void FailsafeControl::stopLfsDutyTimer(void)
+{
+ debug("\r%s:5s-> invoked\n", __func__, id.c_str());
+ memset(&duty_timer, 0, sizeof(duty_timer));
+}
+
+//
+// method: dutyOnExpired
+// description: returns true if ON cycle has expired; false otherwise
+//
+// @param none
+// @return none
+//
+bool FailsafeControl::dutyOnExpired(void)
+{
+ return (time(0) >= duty_timer.offTime);
+}
+
+//
+// method: dutyOffExpired
+// description: returns true if OFF cycle has expired; false otherwise
+//
+// @param none
+// @return none
+//
+bool FailsafeControl::dutyOffExpired(void)
+{
+ return (duty_timer.expirationTime < time(NULL));
+}
+
+//
+// method: sendMailToOutput
+// description: send mail to the output task
+//
+// @param io_tag -> input/output tag
+// @param action -> ON, OFF, UNREGISTER
+// @return none
+//
+void FailsafeControl::sendMailToOutput(OutputAction action)
+{
+ logInfo("%s: failsafe control %s attempting to send action %d\n",
+ __func__, id.c_str(), action);
+
+ if ( isVirtualOutput ) {
+ debug("%s:%s-> updating the virtual output %s\n", __func__, id.c_str(), output.c_str());
+ ModbusMasterWriteRegister(this->output,
+ (action == ACTION_CONTROL_ON) ? 1.0 : 0.0);
+ } else {
+ OutputControlMsg_t *output_mail = OutputMasterMailBox.alloc();
+ memset(output_mail, 0, sizeof(OutputControlMsg_t));
+
+ output_mail->action = action;
+ output_mail->controlType = CONTROL_FAILSAFE;
+ output_mail->priority = this->priority;
+
+ strncpy(output_mail->input_tag, this->input.c_str(), sizeof(output_mail->input_tag)-1);
+ strncpy(output_mail->output_tag, this->output.c_str(), sizeof(output_mail->output_tag)-1);
+ strncpy(output_mail->id, this->id.c_str(), sizeof(output_mail->id)-1);
+
+ OutputMasterMailBox.put(output_mail);
+ }
+}
+
+
+//
+// method: unregisterControl
+// description: unregister this control with the output task
+//
+// @param none
+// @return none
+//
+void FailsafeControl::unregisterControl(void)
+{
+ logInfo("%s: %s attempting to unregister %s\n",
+ __func__, id.c_str(), controlFile.c_str());
+
+ if ( isVirtualOutput ) {
+ debug("%s: %s attempting to unregister virtual output %s\n",
+ __func__, id.c_str(), output.c_str());
+ ModbusMasterWriteRegister(output, 0.0);
+ } else {
+ OutputControlMsg_t *output_mail = OutputMasterMailBox.alloc();
+ memset(output_mail, 0, sizeof(OutputControlMsg_t));
+
+ output_mail->action = ACTION_CONTROL_UNREGISTER;
+ output_mail->controlType = CONTROL_FAILSAFE;
+ output_mail->priority = this->priority;
+ strncpy(output_mail->output_tag, this->output.c_str(), sizeof(output_mail->output_tag)-1);
+ strncpy(output_mail->id, this->id.c_str(), sizeof(output_mail->id)-1);
+
+ OutputMasterMailBox.put(output_mail);
+ }
+}
+
+//
+// method: display
+// description: display the pertinents
+//
+// @param none
+// @return none
+//
+void FailsafeControl::display(void)
+{
+ const char *mapper[] = { "INIT",
+ "START",
+ "OFF",
+ "ON_LFS",
+ "OFF_LFS",
+ "ON_HFS",
+ "OFF_HFS",
+ "SENSOR_ERR",
+ "invalid"
+ };
+
+ printf("\r\n");
+ std::cout << left << setw(10) << setfill(' ') << "failsafe: ";
+ std::cout << left << setw(40) << setfill(' ') << controlFile;
+ std::cout << left << setw(20) << setfill(' ') << id;
+ std::cout << left << setw(6) << setfill(' ') << priority;
+ std::cout << left << setw(20) << setfill(' ') << input;
+ std::cout << left << setw(20) << setfill(' ') << output;
+ std::cout << left << setw(16) << setfill(' ') << mapper[currentState];
+ std::cout << left << setw(12) << setfill(' ') << "lfs-> "
+ << lfs_data.value << ":" << lfs_data.dutyCycle << ":" << lfs_data.interval;
+ std::cout << right << setw(12) << setfill(' ') << "hfs-> "
+ << hfs_data.value << ":" << hfs_data.dutyCycle << ":" << hfs_data.interval;
+
+ std::cout.flush();
+}
\ No newline at end of file