Erick / Mbed 2 deprecated ICE-F412

Dependencies:   mbed-rtos mbed

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();
}