Erick / Mbed 2 deprecated ICE-F412

Dependencies:   mbed-rtos mbed

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

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

File content as of revision 0:61364762ee0e:

/******************************************************************************
*
* File:                SequenceControl.cpp
* Desciption:          ICE Sequence Control class implementation
*
*****************************************************************************/

#include "SequenceControl.h"
#include "ICELog.h"
#include "cJSON.h"
#include "global.h"

#include <stdlib.h>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <stdarg.h>
#include <assert.h>
#include "ModbusMasterApi.h"
#include "ConfigurationHandler.h"

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

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

//
// method:          load
// decsription:     load data from the control file
//
// @param[in]       _controlFile -> the control file
// @param[out]      none
// @return          true on success; false on error
//
bool SequenceControl::load(std::string _controlFile)
{
    controlFile = _controlFile;

    // read the data into a buffer
    char dataBuf[MAX_FILE_SIZE*3];
    bool rc = GLOBAL_mdot->readUserFile(controlFile.c_str(), (void *)dataBuf, sizeof(dataBuf));
    if ( rc != true ) {
        logError("%s: failed to read %d bytes from %s", __func__, sizeof(dataBuf), controlFile.c_str());
        // caller should destroy the object
        return false;
    }

    // validate the JSON tags in the control file
    if ( validateControlData(dataBuf) != true ) {
        logError("%s: invalid control data.", __func__);
        return false;
    }

    // assign object data from control file
    copyControlData(dataBuf);
    
    // TODO: perform additional validation 

    return true;
}



// method:          validateControlData
// description:     validates the data in the control file
//
// @param[in]       dataBuf -> JSON formatted string
// @param[out]      non
// @return          true if valid; false otherwise
//
bool SequenceControl::validateControlData(const char *dataBuf)
{
    // parse the json data
    bool rc = true;
    cJSON * root = cJSON_Parse(dataBuf);

    // parse the control header
    if ( !cJSON_HasObjectItem(root, "id") ||
            !cJSON_HasObjectItem(root, "startTrigger") ||
            !cJSON_HasObjectItem(root, "sequence") ) {
        logError("Sequence Control is missing expected tags");
        cJSON_Delete(root);
        return false;
    }

    // validate the sequence table
    cJSON *sequenceTable  = cJSON_GetObjectItem(root, "sequence");
    for ( int i = 0; i < cJSON_GetArraySize(sequenceTable); ++i ) {
        cJSON *entry = cJSON_GetArrayItem(sequenceTable, i);
        if ( !cJSON_HasObjectItem(entry, "startTrigger") ) {
            logError("Sequence Table missing startTrigger tag");
            rc = false;
            break;
        }
        if ( !cJSON_HasObjectItem(entry, "actions") ) {
            logError("Sequence table is missing actions tag");
            rc = false;
            // FIXME: finish the internals 
            break;
        }
        if ( !cJSON_HasObjectItem(entry, "stopTrigger") ) {
            logError("Sequence table is missing stopTrigger tag");
            rc = false;
            break;
        }
    }

    cJSON_Delete(root);
    return rc;
}

//
// method:          copyControlData
// description:     copy JSON formatted control data to object
//
// @param[in]       dataBuf -> JSON formatted data
// @param[out]      none
// @return          none
//
void SequenceControl::copyControlData(const char* dataBuf)
{
    cJSON *root = cJSON_Parse(dataBuf);

    id           = cJSON_GetObjectItem(root, "id")->valuestring;
    startTrigger = cJSON_GetObjectItem(root, "startTrigger")->valuestring;

    // validate the sequence table
    cJSON *sequenceTable  = cJSON_GetObjectItem(root, "sequence");
    for ( int i = 0; i < cJSON_GetArraySize(sequenceTable); ++i ) {
        cJSON *entry = cJSON_GetArrayItem(sequenceTable, i);

        SequenceEntry n;

        n.startTrigger = cJSON_GetObjectItem(entry, "startTrigger")->valuestring;
        n.stopTrigger  = cJSON_GetObjectItem(entry, "stopTrigger")->valuestring;

        cJSON *actionList = cJSON_GetObjectItem(entry, "actions");
        for ( int j = 0; j < cJSON_GetArraySize(actionList); ++j ) {
            cJSON *list = cJSON_GetArrayItem(actionList, j);
            Action_t a;
            a.action = cJSON_GetObjectItem(list, "action")->valuestring;
            a.id     = cJSON_GetObjectItem(list, "id")->valuestring;
            if ( a.action == "assign" ) {
                a.value = atof(cJSON_GetObjectItem(list, "val")->valuestring);
            } else {
                a.value = 0;
            }
            n.actions.push_back(a);
        }
        // push this entry onto this sequence table
        this->sequenceTable.push_back(n);
    }

    cJSON_Delete(root);
}

//
// method:          start
// decsription:     start the sequence control
//
// @param[in]       none
// @param[out]      none
// @return          none
//
void SequenceControl::start(void)
{
    currentState = SEQ_STATE_START;
}

//
// method:          run
// decsription:     run the sequence control (performs the updates)
//
// @param[in]       none
// @param[out]      none
// @return          OK on success; error otherwise
//
SequenceControlError_t SequenceControl::run(void)
{
    SequenceControlError_t rc = SEQUENCE_CONTROL_OK;

    switch ( this->currentState ) {
        case SEQ_STATE_INIT:
            // do nothing
            break;
        case SEQ_STATE_START:
            // here we need to wait for the start trigger to happen
            if ( this->isControlStartTriggerOn() ) {
                debug("\r%s: [START]->start trigger->[LOADING]\n", id.c_str());
                ModbusValue val;
                ModbusMasterReadRegister(startTrigger, &val);
                debug("\r%s:%s: val.value = %f\n", id.c_str(), startTrigger.c_str(), val.value);
                currentState = SEQ_STATE_LOADING_NEXT_ENTRY;
            } else {
                // just continue waiting for the start trigger to fire
            }
            break;
        case SEQ_STATE_LOADING_NEXT_ENTRY: {
            // this is a transient state
            bool rc = this->loadNextEntry();
            if ( rc ) {
                debug("\r%s: [LOADING]->next entry->[WAIT-START]\n", id.c_str());
                currentState = SEQ_STATE_WAITING_START;
            } else {
                debug("\r%s: [LOADING]->no entries->[FINISHED]\n", id.c_str());
                currentState = SEQ_STATE_FINISHED;
            }
            break;
        }
        case SEQ_STATE_WAITING_START:
            // wait for the start triggers to evaluate to true
            if ( this->isCurrentEntryStartTriggerOn() ) {
                debug("\r%s: [WAIT-START]->perform actions->[WAIT-STOP]\n", id.c_str());
                performActions();
                currentState = SEQ_STATE_WAITING_STOP;
            } else {
                // wait until the start trigger fires
            }
            break;
        case SEQ_STATE_WAITING_STOP:
            // wait for the stop triggers to evaluate to true
            if ( this->isCurrentEntryStopTriggerOn() ) {
                debug("\r%s: [WAIT-STOP]->stop trigger ON->[LOAD NEXT]\n", id.c_str());
                currentState = SEQ_STATE_LOADING_NEXT_ENTRY;
            } else {
                // continue waiting
            }
            break;
        case SEQ_STATE_FINISHED:
            // do any cleanup work that's needed here.
            debug("\r%s: [FINISHED]->clearing the reg->[START]\n", id.c_str());
            ModbusMasterWriteRegister(this->startTrigger, 0);
            this->currentState = SEQ_STATE_START;
            break;
        case SEQ_STATE_MAX:
        default:
            logError("%s: unknown state %u\n", __func__, this->currentState);
            rc = SEQUENCE_CONTROL_UNK_STATE;
            break;
    }
    return rc;
}

//
// method:          isControlStartTriggerOn
// description:     true if the start trigger evals to true
//
// @param[in]       none
// @param[out]      none
// @return          true if start trigger > 0; false otherwise
//
bool SequenceControl::isControlStartTriggerOn(void)
{
    ModbusValue value;
    bool rc = ModbusMasterReadRegister(this->startTrigger, &value);
    if ( rc && value.value ) {
        return true;
    }

    return false;
}

//
// method:          isCurrentEntryStartTriggerOn
// description;     true the current sequence entry start trigger evals to true
//
// @param[in]       none
// @param[out]      none
// @return          none
//
bool SequenceControl::isCurrentEntryStartTriggerOn(void)
{
    ModbusValue value;
    bool rc = ModbusMasterReadRegister(this->currentEntry.startTrigger, &value);
    if ( rc != true ) {
        logError("%s: failed to read %s from modbus master",
                 __func__, this->currentEntry.startTrigger.c_str());
        return rc;
    } else if ( value.value ) {
        debug("\r%s:%s-> returning true\n", __func__, id.c_str());
        return true;
    }
    return false;
}

//
// method:          isCurrentEntryStopTriggerOn
// description;     true the current sequence entry start trigger evals to true
//
// @param[in]       none
// @param[out]      none
// @return          none
//
bool SequenceControl::isCurrentEntryStopTriggerOn(void)
{
    ModbusValue value;
    bool rc = ModbusMasterReadRegister(this->currentEntry.stopTrigger, &value);
    if ( rc != true ) {
        logError("%s: failed to read %s from modbus master",
                 __func__, this->currentEntry.stopTrigger.c_str());
        return rc;
    } else if ( value.value ) {
        debug("\r%s:%s-> returning true\n", __func__, id.c_str());
        return true;
    }
    return false;
}

//
// method:          loadNextEntry
// description:     load the next entry from the sequence table
//
// @param[in]       none
// @param[out[      none
// @return          true is loaded; false otherwise
//
bool SequenceControl::loadNextEntry(void)
{
    if ( this->sequenceTable.empty() ) {
        debug("\r%s: sequence table is empty\n", id.c_str());
        return false;
    }
    if ( nextEntryIndex < this->sequenceTable.size() ) {
        currentEntry = sequenceTable.at(nextEntryIndex++);
        printf("\r...successfully loaded new entry\n");
        return true;
    }
    return false;
}

//
// method:          performActions
// description:     perform the actions associated with the current entry
//
// @param[in]       none
// @param[out]      none
// @return          none
//
SequenceControlError_t SequenceControl::performActions(void)
{
    SequenceControlError_t rc = SEQUENCE_CONTROL_OK;

    // possible action types:
    //      create      -> create a control
    //      delete      -> delete a control
    //      modify      -> modify a control
    //      assign      -> assign a value to a modbus register 
    //      execute     -> execute a script
    if ( !currentEntry.actions.empty() ) {
        std::vector<Action_t>::const_iterator pos;
        for ( pos = currentEntry.actions.begin(); pos != currentEntry.actions.end(); ++pos ) {
            debug("\raction->%s on ID->%s\n", pos->action.c_str(), pos->id.c_str());
            // based on the action, we need to determine what to do
            // 1: determine the action
            // 2: see if the file exists (catastrophic)
            // 3: if it's a create or delete action, send a message to the
            //    configuration handler
            if ( pos->action == "createsp" ) {
                rc = createSubControl(pos->id, CONTROL_SETPOINT);
                assert(!rc);
            } else if ( pos->action == "createtm" ) {
                rc = createSubControl(pos->id, CONTROL_TIMER);
                assert(!rc);
            } else if ( pos->action == "deletesp" ) {
                rc = destroySubControl(pos->id, CONTROL_SETPOINT);
                assert(!rc);
            } else if ( pos->action == "deletetm" ) {
                rc = destroySubControl(pos->id, CONTROL_TIMER);
                assert(!rc);
            } else if ( pos->action == "execute"  ) {
                  // not implemented 
            } else if ( pos->action == "assign"   ) {
              if ( assignRegister(pos->id, pos->value) != true ) {
                  assert(0);
              }
            } else {
                logError("%s: unknown action %s (%s)", __func__, pos->action.c_str(), pos->id.c_str());
                rc = SEQUENCE_CONTROL_BAD_ACTION;
            }
        }
    } else {
        logInfo("%s: no entries in the action table", __func__);
    }
    return rc;
}

//
// method:              createSubControl
// description:         create a control that's listed in the sequence table
//
// @param[in]           action (create, destroy, etc.)
// @param[in]           id -> control identifier
// @param[out[          none
// @return              OK on success; error otherwise
//
SequenceControlError_t SequenceControl::createSubControl(const std::string controlId,     
                                                         Control_t type) 
{
    std::string file_prefix;
    
    switch (type) {
        case CONTROL_SETPOINT:
            file_prefix = CONTROL_SP_STR;
            break;
        case CONTROL_TIMER:
            file_prefix = CONTROL_TM_STR;
            break;
        default:
            logError("%s %s unsupported type %d", __func__, id.c_str(), type);
            return SEQUENCE_CONTROL_BAD_CONTROL_TYPE;
    }
    
    char filename[64];
    snprintf(filename, sizeof(filename), "%s%s_%s%s.json", 
               SEQUENCE_CONTROL_FILENAME_PREFIX, 
               this->id.c_str(), 
               file_prefix.c_str(),
               controlId.c_str());
               
    debug("\r%s: searching for %s\n", id.c_str(), filename);
    
    // send a message to the configuration handler to create the control
    ConfigMessage_t *msg  = ConfigHandlerMailBox.alloc();
    memset(msg, 0, sizeof(ConfigMessage_t));
    msg->action  = ACTION_CREATE;
    msg->control = (Control_t)type;
    strncpy(msg->controlFile, filename, sizeof(msg->controlFile)-1);

    printf("%s: Sending a create request for control %s\r\n",
           __func__, msg->controlFile);

    ConfigHandlerMailBox.put(msg);

    return SEQUENCE_CONTROL_OK;
}

//
// method:              destroySubControl
// description:         destroy a control that's listed in the sequence table
//
// @param[in]           action (create, destroy, etc.)
// @param[in]           id -> control identifier
// @param[out[          none
// @return              OK on success; error otherwise
//
SequenceControlError_t SequenceControl::destroySubControl(const std::string controlId,
                                                          Control_t type)
{
    std::string file_prefix;
    
    switch (type) {
        case CONTROL_SETPOINT:
            file_prefix = CONTROL_SP_STR;
            break;
        case CONTROL_TIMER:
            file_prefix = CONTROL_TM_STR;
            break;
        default:
            logError("%s %s unsupported type %d", __func__, id.c_str(), type);
            return SEQUENCE_CONTROL_BAD_CONTROL_TYPE;
    }
    
    char filename[64];
    snprintf(filename, sizeof(filename), "%s%s_%s%s.json", 
               SEQUENCE_CONTROL_FILENAME_PREFIX, 
               this->id.c_str(), 
               file_prefix.c_str(),
               controlId.c_str());
               
    debug("\r%s: searching for %s\n", id.c_str(), filename);

    // send a message to the configuration handler to create the control
    ConfigMessage_t *msg  = ConfigHandlerMailBox.alloc();
    memset(msg, 0, sizeof(ConfigMessage_t));
    msg->action  = ACTION_DESTROY;
    msg->control = (Control_t)type;
    strncpy(msg->controlFile, filename, sizeof(msg->controlFile)-1);

    printf("%s: Sending a create request for control %s\r\n",
           __func__, msg->controlFile);

    ConfigHandlerMailBox.put(msg);

    return SEQUENCE_CONTROL_OK;
}

bool SequenceControl::assignRegister(const std::string id, float value)
{
    return (ModbusMasterWriteRegister(id, value));
}

//
// method:          display
// decsription:     display the control data
//
// @param[in]       none
// @param[out]      none
// @return          none
//
void SequenceControl::display(void)
{
    const char *mapper[] = { "INIT",
                             "START",
                             "LOADING",
                             "WAIT-START",
                             "WAIT-STOP",
                             "FINISHED",
                             "FAILED",
                             "NULL"
                           };

    printf("\rid-> %s\n", id.c_str());
    printf("\rstartTrigger-> %s\n",  startTrigger.c_str());
    printf("\rSequenceTable: \n");

    std::vector<SequenceEntry>::const_iterator seqIt;

    int entry = 0;

    for ( seqIt = sequenceTable.begin(); seqIt != sequenceTable.end(); ++seqIt ) {
        printf("\rEntry %d: %s\n \r  startTrigger: %s\n",
               ++entry,
               (entry == nextEntryIndex-1) ? "<-currently active" : "",
               seqIt->startTrigger.c_str());
        std::vector<Action_t>::const_iterator actIt;
        for ( actIt = seqIt->actions.begin(); actIt != seqIt->actions.end(); ++actIt ) {
            printf("\r\taction-> %s, id-> %s ", actIt->action.c_str(), actIt->id.c_str());
            if ( actIt->action == "assign" ) printf("value -> %f", actIt->value);
            printf("\r\n");
        }
        printf("\r  stopTrigger: %s\n", seqIt->stopTrigger.c_str());
    }

    printf("\r\n\rcurrent state = %s\r\n\r\n", mapper[this->currentState]);
}