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.
ICE-Application/src/ConfigurationHandler/Controls/FailsafeControl.cpp
- Committer:
- jmarkel44
- Date:
- 2017-01-24
- Revision:
- 0:61364762ee0e
File content as of revision 0:61364762ee0e:
/******************************************************************************
*
* 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();
}