Erick / Mbed 2 deprecated ICE-F412

Dependencies:   mbed-rtos mbed

ICE-Application/src/ConfigurationHandler/Controls/SensorErrorControl.cpp

Committer:
jmarkel44
Date:
2017-01-24
Revision:
0:61364762ee0e

File content as of revision 0:61364762ee0e:

/******************************************************************************
*
* File:                SensorErrorControl.cpp
* Desciption:          ICE Sensor Error Control class implementation
*
*****************************************************************************/
#include "SensorErrorControl.h"
#include "cJSON.h"
#include "ModbusMasterApi.h"
#include "utilities.h"
#include "ICELog.h"
#include <string>
#include <iostream>
#include <iomanip>
#include <stdarg.h>

using namespace std;

// for debugging - this can be set via the console command:
//      "debug-se 1
bool debugSensorError = false;

static void debug(const char *fmt, ...)
{
    if ( debugSensorError ) {
        va_list vargs;
        va_start(vargs, fmt);
        vfprintf(stdout, fmt, vargs);
        va_end(vargs);
    }
}

//
// method:              load
// description:         load data from the control file
//
// @param[in]           _controlFile -> the control file
// @return              true if loaded; false otherwise
//
bool SensorErrorControl::load(const 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;
    }

    // validate the JSON formatted control data
    if ( !validateControlData(dataBuf) ) {
        logError("%s: failed to validate control data", __func__);
        return false;
    }

    // copy the control data
    copyControlData(dataBuf);
    
    ModbusValue val;
    // validate the input 
    if ( ModbusMasterReadRegister(input, &val) == false ) {
        logError("%s failed to find input %s", id.c_str(), input.c_str());
        return false;
    }

    isVirtualOutput = Util_isVirtualOutput(output) ? true : false;
    return true;
}

//
// @method:             validateControlData
// @description:        vaqlidate the JSON formatted string
//
// @param[in]           buf -> JSON formatted string
// @param[out]          none
// @return              true if valid; false otherwise
//
bool SensorErrorControl::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, "dutyCycle") ||
            !cJSON_HasObjectItem(root, "interval") ) {
        logError("%s: control file is missing expected tags", __func__);
        rc = false;
    }

    cJSON_Delete(root);
    return rc;
}

//
// method:              copyControlData
// description:         copy the data from the control file to the object
//
// @param[in]           buf -> JSON formatted string
// @param[out]          none
// @return              none
//
void SensorErrorControl::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;
    dutyCycle.dutyCycle = atoi(cJSON_GetObjectItem(root, "dutyCycle")->valuestring);
    dutyCycle.interval  = atoi(cJSON_GetObjectItem(root, "interval")->valuestring);

    cJSON_Delete(root);
}

//
// @method:             start
// @description:        put the control in the start state
//
// @param[in]           none
// @param[out]          none
// @return              none
//
void SensorErrorControl::start(void)
{
    currentState = STATE_START;
}

//
// @method              update
// @description         run the simplified state machine
//
// @param[in]           none
// @param[out]          none
// @return              OK on success; error otherwise
//
SensorErrorControlError_t SensorErrorControl::update(void)
{
    SensorErrorControlError_t rc = SENSOR_ERROR_CONTROL_OK;

    switch (this->currentState) {
        case STATE_INIT:
            // do nothing: control must be programmatically started
            break;
        case STATE_START:
            if ( this->isSensorError() ) {
                if ( dutyCycle.dutyCycle > 0 ) {
                    this->startDutyTimer();
                    this->currentState = STATE_DUTY_CYCLE_ON;
                    sendMailToOutput(ACTION_CONTROL_ON);
                    debug("\r%s: [STARTUP]->sensor error->[DUTY_CYCLE_ON] time=%lu\n", id.c_str(), time(0));
                } else {
                    // this is a fixed-off state
                    this->stopDutyTimer();  // probably not needed
                    this->currentState = STATE_DUTY_CYCLE_OFF;
                    // tell the output task to turn off the relay
                    sendMailToOutput(ACTION_CONTROL_OFF);
                    debug("\r%s: [STARTUP]->sensor error (no duty)->[DUTY_CYCLE_OFF] %lu\n", id.c_str(), time(0));
                }
            } else {
                // input signal is OK
                debug("\r%s: [STARTUP]->OK->[OFF] %lu\n", id.c_str(), time(0));
                this->currentState = STATE_CONTROL_OFF;
            }
            break;
        case STATE_CONTROL_OFF:
            if ( isSensorError() ) {
                if ( dutyCycle.dutyCycle > 0 ) {
                    this->startDutyTimer();
                    this->currentState = STATE_DUTY_CYCLE_ON;
                    sendMailToOutput(ACTION_CONTROL_ON);
                    debug("\r%s: [STARTUP]->sensor error->[DUTY_CYCLE_ON] %lu\n", id.c_str(), time(0));
                } else {
                    // this is a fixed off state
                    this->stopDutyTimer();  // probably not needed
                    this->currentState = STATE_DUTY_CYCLE_OFF;
                    // tell the output task to turn off the relay
                    sendMailToOutput(ACTION_CONTROL_OFF);
                    debug("\r%s: [STARTUP]->sensor error (no duty)->[DUTY_CYCLE_OFF] %lu\n", id.c_str(), time(0));
                }
            } else {
                // input signal is OK, so don't do anything
            }
            break;
        case STATE_DUTY_CYCLE_ON:
            if ( !isSensorError() ) {
                this->stopDutyTimer();
                this->currentState = STATE_CONTROL_OFF;
                this->unregisterControl();
                debug("\r%s: [DUTY_CYCLE_ON]->no error->[OFF] %lu\n", id.c_str(), time(0));

            } else if ( dutyOnExpired() )
                if ( dutyCycle.dutyCycle < 100 ) {
                    this->currentState = STATE_DUTY_CYCLE_OFF;
                    sendMailToOutput(ACTION_CONTROL_OFF);
                    debug("\r%s: [DUTY_CYCLE_ON]->on expired->[DUTY_CYCLE_OFF] %lu\n", id.c_str(), time(0));
                } else {
                    // fixed-on state
                    // do nothing - we'll need to wait for the sensor error
                    // to clear before something can happen
                }
            break;
        case STATE_DUTY_CYCLE_OFF:
            if ( !isSensorError() ) {
                // there is no sensor error, so unregister the control and cleanup
                this->stopDutyTimer();
                this->currentState = STATE_CONTROL_OFF;
                this->unregisterControl();
                debug("\r%s: [DUTY_CYCLE_OFF]->no error->[OFF] %lu\n", id.c_str(), time(0));
                stopDutyTimer();
            } else if ( dutyOffExpired() ) {
                if ( dutyCycle.dutyCycle > 0 ) {
                    // we go back to the OFF state so the start and expiration
                    // times are recalculated
                    this->currentState = STATE_CONTROL_OFF;
                    sendMailToOutput(ACTION_CONTROL_OFF);
                    debug("\r%s: [DUTY_CYCLE_OFF]->off expired->[OFF] %lu\n", id.c_str(), time(0));
                } else {
                    // fixed-off state 
                    // do nothing - we'll need to wait for the sensor error
                    // to clear before something can happen
                }
            }
            break;
        default:
            logError("%s: unknown state\n", __func__);
            rc = SENSOR_ERROR_CONTROL_UNK_STATE;
            break;
    }
    return rc;
}

//
// @method:             isSensorError
// @description:        reads the modbus register to get the error condition of
//                      the input signal
//
// @param[in]           none
// @param[out]          none
// @return              true if in error; false otherwise
//
bool SensorErrorControl::isSensorError()
{
    ModbusValue val;

    if ( ModbusMasterReadRegister(this->input, &val) != true ) {
        logError("Failed to read %s\n", this->input.c_str());
        return true;
    } else if ( val.errflag ) {
        // input is in sensor error
        return true;
    }
    return false;
}

//
// @method:             dutyOnExpired
// @description:        returns true of duty ON time expired
//
// @param[in]           none
// @param[out]          none
// @return              true if expired; false otherwise
//
bool SensorErrorControl::dutyOnExpired(void)
{
    return (time(0) >= duty_timer.offTime);
}

//
// @method:             dutyOffExpired
// @description:        returns true of duty OFF time expired
//
// @param[in]           none
// @param[out]          none
// @return              true if expired; false otherwise
//
bool SensorErrorControl::dutyOffExpired(void)
{
    return (duty_timer.expirationTime < time(0));
}

//
// method:          startDutyTimer
// descrption:      start the sensor error duty-timer
//
// @param           none
// @return          none
//
void SensorErrorControl::startDutyTimer(void)
{
    unsigned long currentTime = time(0);
    unsigned long period      = dutyCycle.interval * 60;

    duty_timer.offTime        = currentTime + ((double)period * ((double)dutyCycle.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:          stopDutyTimer
// descrption:      stop the sensor error duty-timer
//
// @param           none
// @return          none
//
void SensorErrorControl::stopDutyTimer(void)
{
    memset(&duty_timer, 0, sizeof(duty_timer));
}

//
// method:          sendMailToOutput
// description:     send a message to the output thread
//
// @param[in]       action -> on, off, etc.
// @param[out]      none
// @return
//
void SensorErrorControl::sendMailToOutput(OutputAction 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 {
        debug("%s:%s sensor error control attempting to send action %d\n",
              __func__, id.c_str(), action);

        OutputControlMsg_t *output_mail = OutputMasterMailBox.alloc();
        memset(output_mail, 0, sizeof(OutputControlMsg_t));

        output_mail->action         = action;
        output_mail->controlType    = CONTROL_SENSOR_ERROR;
        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 the control with the output task
// 
// @param[in]           none
// @param[out]          none
// @return              none
//
void SensorErrorControl::unregisterControl(void)
{
    if ( isVirtualOutput ) {
        debug("%s: %s attempting to unregister virtual output %s\n", __func__, id.c_str(), output.c_str());
        ModbusMasterWriteRegister(output, 0.0);
    } else {
        debug("%s: %s attempting to unregister output %s\n",
              __func__, id.c_str(), output.c_str());

        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:        show the pertinents
//
// @param[in]           none
// @param[out]          none
// @return              none
//    
void SensorErrorControl::display(void)
{
    const char *mapper[] = { "INIT",
                             "START",
                             "OFF",
                             "DUTY CYCLE ON",
                             "DUTY CYCLE OFF",
                             "invalid"
                           };

    printf("\r\n");
    std::cout << left << setw(10) << setfill(' ') << "sensor error: ";
    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 << "duty cycle: " << left << setw(4) << setfill(' ') << dutyCycle.dutyCycle;
    std::cout << "interval: "   << left << setw(4) << setfill(' ') << dutyCycle.interval;

    std::cout.flush();
}