Erick / Mbed 2 deprecated ICE-F412

Dependencies:   mbed-rtos mbed

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