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/OutputTask/OutputTask.cpp
- Committer:
- jmarkel44
- Date:
- 2017-01-24
- Revision:
- 0:61364762ee0e
File content as of revision 0:61364762ee0e:
/******************************************************************************
*
* File: OutputTask.cpp
* Desciption: source for the ICE Output task
*
*****************************************************************************/
#include <vector>
#include <string>
#include <algorithm>
#include <assert.h>
#include <time.h>
#include "OutputTask.h"
#include "global.h"
#include "cJSON.h"
#include "ModbusMasterApi.h"
#include "LoggerApi.h"
#include "Control.h"
#include "rtc.h"
#include "ICELog.h"
//#include "../add-ons/MTSLog/MTSLog.h"
// local functions
static int createOutput(const char *controlFile);
static int loadPersistentOutputs(void);
static void writeOutputs(const std::string, const std::string);
static int enableOutputReqHandler(OutputControlMsg_t *msg);
static int disableOutputReqHandler(OutputControlMsg_t *msg);
static int unregisterControl(const char *id, unsigned int pri, const char *output);
static void dumpEventRecord(EventReasonStruct_t &ev);
//
// The Output Map
//
// this is the main data structure used to distinguish which control has
// priority of an output. the layout is as-follows:
//
// outputMap["o_rly1"]-> Control<"ManControl_rly1", 100, ON > <-- highest pri control manipulates relay
// Control<"Flowswitch", 700 OFF> <-- lower pri (queued up)
// Control<"SetpointControl_rly1", 800, OFF> <-- lowest pri (queued up)
//
// outputMap["o_rly2"]-> Control<"Flowswitch", 700, OFF>
// -> Control<"SetpointControl_rly2", 800, OFF>
//
// outputMap["o_rly3"]-> Control<"ManControl_rly3", 100, ON >
// Control<"Flowswitch", 700, ON >
// Control<"TimerControl_rly3", 750, ON >
//
//
// The Control Vector (per relay) is always sorted by priority, whereas
// the highest priority control (lower priority number) is at the front
// (v.front()) of the list.
//
typedef std::map<std::string, std::vector<Control> > StringOutputVector_t;
static StringOutputVector_t outputMap;
// operator for sorting the outputs vectors
bool operator<(const Control &control1, const Control &control2)
{
return control1.getPriority() < control2.getPriority();
}
/*****************************************************************************
* Function: OutputTask
* Description: Main entry point for the Output Task
*
* @param args -> not used
* @return none
*****************************************************************************/
void OutputTask(void const *args)
{
int rc;
(void)args;
printf("\r%s has started...\n", __func__);
#ifdef LOAD_PERSISTENT_CONFIGURATIONS
if ( loadPersistentOutputs() != 0 ) {
logError("Failed to load persistent outputs");
}
#endif
// signal the main thread to continue
osSignalSet(mainThreadId, sig_config_continue);
while (true) {
// wait for a message
osEvent evt = OutputMasterMailBox.get();
if (evt.status == osEventMail) {
OutputControlMsg_t *msg = (OutputControlMsg_t*) evt.value.p;
switch ( msg->action ) {
case ACTION_NEW:
// read the file and and create an output entry
rc = createOutput(msg->controlFile);
if ( rc != 0 ) {
logError("%s: failed to create output %s\n",
__func__, msg->controlFile);
}
break;
case ACTION_CONTROL_ON:
logInfo("%s is requesting ON control of %s", msg->id, msg->output_tag);
rc = enableOutputReqHandler(msg);
if ( rc != 0 ) {
logError("%s: failed to enable output for %s",
__func__, msg->id);
}
break;
case ACTION_CONTROL_OFF:
logInfo("%s is requesting OFF control of %s", msg->id, msg->output_tag);
rc = disableOutputReqHandler(msg);
if ( rc != 0 ) {
logError("%s: failed to disable output for %s",
__func__, msg->id);
}
break;
case ACTION_CONTROL_UNREGISTER:
logInfo("%s is requesting its deletion from %s", msg->id, msg->output_tag);
rc = unregisterControl(msg->id, msg->priority, msg->output_tag);
if ( rc != 0 ) {
logError("%s: failed to unregister control %s",
__func__, msg->id);
}
break;
default:
logError("%s unknown action %u", __func__, msg->action);
break;
}
// free the message
OutputMasterMailBox.free(msg);
// refresh the outputs
writeOutputs(msg->id, msg->output_tag);
}
}
}
/*****************************************************************************
* Function: publishEvent
* Description: publish an event to the logger
*
* @param output -> the output channel
* @param control -> the control, this can be null
* @return none
*****************************************************************************/
void publishEvent(std::string output, const Control *control)
{
EventReasonStruct_t ev;
ModbusValue input_value;
ModbusValue output_value;
memset(&ev, 0, sizeof(ev));
// if there's no control, that means the only control that was on
// the relay stack has been destroyed, so we'll send a NO CONTROL
// event code.
if ( !control ) {
ev.eventReason = EVENT_REASON_NO_CONTROL;
ModbusMasterReadRegister(output, &output_value);
strncpy(ev.outputTag, output.c_str(), sizeof(ev.outputTag));
ev.outputValue = output_value.value;
dumpEventRecord(ev);
EventLoggerApi(ev);
return;
}
switch ( control->getControlType() ) {
case CONTROL_SETPOINT:
case CONTROL_COMPOSITE:
ev.eventReason = EVENT_REASON_AUTO;
strncpy(ev.inputTag, control->getInput().c_str(), sizeof(ev.inputTag));
strncpy(ev.outputTag, output.c_str(), sizeof(ev.outputTag));
ModbusMasterReadRegister(control->getInput(), &input_value);
ModbusMasterReadRegister(output, &output_value);
ev.inputValue = input_value.value;
ev.outputValue = output_value.value;
dumpEventRecord(ev);
EventLoggerApi(ev);
break;
case CONTROL_MANUAL:
ev.eventReason = EVENT_REASON_MANUAL;
strncpy(ev.outputTag, output.c_str(), sizeof(ev.outputTag));
ModbusMasterReadRegister(output, &output_value);
ev.outputValue = output_value.value;
dumpEventRecord(ev);
EventLoggerApi(ev);
break;
case CONTROL_TIMER:
ev.eventReason = EVENT_REASON_TIMER;
strncpy(ev.outputTag, output.c_str(), sizeof(ev.outputTag));
ModbusMasterReadRegister(output, &output_value);
ev.outputValue = output_value.value;
dumpEventRecord(ev);
EventLoggerApi(ev);
break;
case CONTROL_FAILSAFE:
case CONTROL_SENSOR_ERROR:
ev.eventReason = EVENT_REASON_FAILSAFE;
strncpy(ev.inputTag, control->getInput().c_str(), sizeof(ev.inputTag));
strncpy(ev.outputTag, output.c_str(), sizeof(ev.outputTag));
ModbusMasterReadRegister(control->getInput(), &input_value);
ModbusMasterReadRegister(output, &output_value);
ev.inputValue = input_value.value;
ev.outputValue = output_value.value;
dumpEventRecord(ev);
EventLoggerApi(ev);
break;
default:
logError("%s: unknown control type\n", __func__);
break;
}
}
/*****************************************************************************
* Function: writeOutputs
* Description: send a message to the modbus master of who's in control
*
*
* @param[in] id -> control identifier
* @param[in] output_tag -> the output to write
* @return none
*****************************************************************************/
static void writeOutputs(const std::string id, const std::string output_tag)
{
(void) id;
if ( output_tag.empty() ) {
logError("%s: invalid output tag", __func__);
return;
}
StringOutputVector_t::const_iterator pos;
// find the output
pos = outputMap.find(output_tag);
if ( pos != outputMap.end() ) {
if ( pos->second.empty() ) {
// we found the output but nothing's controlling it...
ModbusMasterWriteRegister(pos->first, RELAY_STATUS_NOT_CONTROLLED);
publishEvent(pos->first, NULL);
} else {
// get the mapped state for the highest priority control
int mappedState = pos->second.begin()->getMappedState();
// read the register value for this output
ModbusValue value;
ModbusMasterReadRegister(pos->first, &value);
// a new higher priority control is manipulating the output,
// so send the new values to the modbus and publish an event
if ( value.value != mappedState ) {
ModbusMasterWriteRegister(pos->first, mappedState);
Control t = *(pos->second.begin());
publishEvent(pos->first, &t);
}
}
// TODO: This may be a virtual output, so we'll need to see if it
// exists in the vregmap
} else {
logError("%s failed to find the selected output %s",
__func__, output_tag.c_str());
}
}
/*****************************************************************************
* Function: createOutput
* Description:
*
* @param outputFile -> name of output file
* @return none
*****************************************************************************/
static int createOutput(const char *outputFile)
{
char dataBuf[MAX_FILE_SIZE];
bool status = GLOBAL_mdot->readUserFile(outputFile, (void *)dataBuf, sizeof(dataBuf));
if ( status != true ) {
logError("%s failed to read %s", __func__, outputFile);
return -1;
}
cJSON * root = cJSON_Parse(dataBuf);
if ( !cJSON_HasObjectItem(root, "id") ) {
logError("%s: error extracting expected tags", __func__);
cJSON_Delete(root);
return -1;
}
std::string id = cJSON_GetObjectItem(root,"id")->valuestring;
cJSON_Delete(root);
std::vector<Control> v;
outputMap[id] = v;
return 0;
}
/*****************************************************************************
* Function: enableOutputReqHandler
* Description: handle a request to enable an output
*
* @param[in] msg -> the message request
* @return -1 on error
*****************************************************************************/
static int enableOutputReqHandler(OutputControlMsg_t *msg)
{
// attempt to find the output in the map
StringOutputVector_t::iterator pos;
pos = outputMap.find(msg->output_tag);
if ( pos == outputMap.end() ) {
logError("%s: failed to find the designated output %s\n",
__func__, msg->output_tag);
return -1;
}
if ( pos->second.empty() ) {
// this is a new request
std::string cid(msg->id);
std::string input(msg->input_tag);
Control c(cid, msg->controlType, input, msg->priority, CONTROL_ON);
pos->second.push_back(c);
} else {
// find this control in the list
std::vector<Control>::iterator v;
for ( v = pos->second.begin(); v != pos->second.end(); ++v ) {
if ( strcmp(v->getId().c_str(), msg->id) == 0 ) {
v->setState(CONTROL_ON);
break;
}
}
if ( v == pos->second.end() ) {
// this is a new request, so add it and sort the vector
std::string cid(msg->id);
std::string input(msg->input_tag);
Control c(cid, msg->controlType, input, msg->priority, CONTROL_ON);
pos->second.push_back(c);
std::sort(pos->second.begin(), pos->second.end());
}
}
return 0;
}
/*****************************************************************************
* Function: disableOutputReqHandler
* Description: handle a request to disable an output
*
* @param[in] msg -> the message request
* @return none
*****************************************************************************/
static int disableOutputReqHandler(OutputControlMsg_t *msg)
{
// attempt to find the output in the map
StringOutputVector_t::iterator pos;
pos = outputMap.find(msg->output_tag);
if ( pos == outputMap.end() ) {
logError("%s: failed to find the designated output %s\n",
__func__, msg->output_tag);
return -1;
}
// if the control list is empty, push this control on the list
if ( pos->second.empty() ) {
std::string cid(msg->id);
std::string input(msg->input_tag);
Control c(cid, msg->controlType, input, msg->priority, CONTROL_OFF);
pos->second.push_back(c);
} else {
// find this control in the list
std::vector<Control>::iterator v;
for ( v = pos->second.begin(); v != pos->second.end(); ++v ) {
if ( strcmp(v->getId().c_str(), msg->id) == 0 ) {
v->setState(CONTROL_OFF);
break;
}
}
if ( v == pos->second.end() ) {
// this is a new request, so add it and sort the vector
std::string cid(msg->id);
std::string input(msg->input_tag);
Control c(cid, msg->controlType, input, msg->priority, CONTROL_OFF);
pos->second.push_back(c);
std::sort(pos->second.begin(), pos->second.end());
}
}
return 0;
}
/*****************************************************************************
* Function: unregisterControl
* Description:
*
* @param id -> control identifier
* @param pri -> priority
* @param output -> output (e.g. "o_rly05")
*
* @return 0 on success; -1 on error
*****************************************************************************/
static int unregisterControl(const char *id, unsigned int pri, const char *output)
{
// attempt to find the output in the map
StringOutputVector_t::iterator pos;
bool found = false;
pos = outputMap.find(output);
if ( pos == outputMap.end() ) {
logError("%s: failed to find the designated output %s\n",
__func__, output);
return -1;
}
// find the control in the list
std::vector<Control>::iterator v;
for ( v = pos->second.begin(); v != pos->second.end(); ++v) {
if ( strcmp(v->getId().c_str(), id) == 0 ) {
pos->second.erase(v);
found = true;
break;
}
}
if ( !found ) {
logError("%s: failed to find control %s in list", __func__, id);
return -1;
}
return 0;
}
/*****************************************************************************
* Function: loadPersistentOutputs
* Description: build the output map
*
* @param none
* @return 0 on success; -1 of failure
*****************************************************************************/
static int loadPersistentOutputs(void)
{
bool status;
cJSON *root;
int rc = 0;
printf("\rLoading persistent outputs:\n");
std::vector<std::string> file_list = GLOBAL_mdot->listUserFiles();
for (std::vector<std::string>::const_iterator i = file_list.begin(); i != file_list.end(); ++i) {
// load in all of the output files
if( strncmp( i->c_str(), OUTPUT_STR, strlen(OUTPUT_STR)) == 0 ||
strncmp( i->c_str(), VOUTPUT_STR, strlen(VOUTPUT_STR)) == 0) {
char scratchBuf[MAX_FILE_SIZE];
status = GLOBAL_mdot->readUserFile(i->c_str(), scratchBuf, MAX_FILE_SIZE);
if( status != true ) {
logError("%s: failed to read %s", __func__, i->c_str());
rc = -1;
} else {
logInfo("%s: successfully read %s", __func__, i->c_str());
}
root = cJSON_Parse(scratchBuf);
std::string id = cJSON_GetObjectItem(root,"id")->valuestring;
printf("\r output %s loaded\n", i->c_str());
cJSON_Delete(root);
// emplace the empty control vector into the output map
std::vector<Control> v;
outputMap[id] = v;
}
}
return rc;
}
/*****************************************************************************
* Function: dumpEventRecord
* Description: display the contents of an event record
*
* @param[in] ev -> the event structure
* @return none
*****************************************************************************/
static void dumpEventRecord(EventReasonStruct_t &ev)
{
const char *mapper[] = { "auto",
"manual",
"timer",
"flow",
"failsafe",
"no control"
};
time_t rawtime;
time(&rawtime);
int iyr=0, imo=0, idy=0, ihr=0, imn=0, isc=0;
#undef TODO_ICE
#if 0
rtc_get_time(&iyr, &imo, &idy, &ihr, &imn, &isc);
#endif
printf("\rEVENT RECORD (%04d-%02d-%02d %02d:%02d:%02d)\r\n",
iyr, imo, idy, ihr, imn, isc);
printf("\rev.eventReason = %d (%s)\n", ev.eventReason, mapper[ev.eventReason]);
printf("\rev.inputTag = %s\n", ev.inputTag[0] ? ev.inputTag : "empty");
printf("\rev.outputTag = %s\n", ev.outputTag[0] ? ev.outputTag : "empty");
printf("\rev.inputValue = %.02f\n", ev.inputValue);
printf("\rev.outputValue = %.02f (%s)\n", ev.outputValue,
((unsigned int)ev.outputValue & 1 == 1) ? "on" : "off");
}
/*****************************************************************************
* Function: DisplayOutputs
* Description: Display a list of outputs and their controls
*
* @param none
* @return none
*****************************************************************************/
void DisplayOutputs(void)
{
StringOutputVector_t::iterator pos;
for ( pos = outputMap.begin(); pos != outputMap.end(); ++pos ) {
if ( pos->second.empty() ) {
printf("\r [%s]-> [no controls] \n", pos->first.c_str());
} else {
printf("\r [%s]-> ", pos->first.c_str());
std::vector<Control>::iterator i;
for ( i = pos->second.begin(); i != pos->second.end(); ++i ) {
i->display();
}
printf("\n");
}
}
printf("\r\n");
}