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/SequenceControl.cpp
- Committer:
- jmarkel44
- Date:
- 2017-01-24
- Revision:
- 1:b2e90cda7a5a
- Parent:
- 0:61364762ee0e
File content as of revision 1:b2e90cda7a5a:
/******************************************************************************
*
* 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]);
}