#include "ess.h"

#define CHECK_RES() CheckESSResult(res, __LINE__)

static ESS_HANDLE hDev;
static ESS_RESULT res;
static ESS_STATISTICS essStats;
static ESC_STATE currESCState;
static ESS_TIMESTAMP t;

static uint32_t input1;
static uint32_t output1;

/* Default PDO assignment: (set in SetDefaultPDOs()) */
static const uint16_t SM2DefPDOs[] = { 0x1600 };
static const uint16_t SM3DefPDOs[] = { 0x1a00 };
static const ESS_PDO_ENTRY PDO1600Entries[] = { ESS_MAP_ENTRY(0x2000, 0, 32) };
static const ESS_PDO_ENTRY PDO1a00Entries[] = { ESS_MAP_ENTRY(0x2010, 0, 32) };

/* SM configuration: (Assuming ESC memory 0x1000..0x2fff here: OK for ET1100, but with other ESCs with less than 8k RAM you might have to realign these sample SM areas) */
static const ESS_SM_CONFIGURATION smConfigs[] =
{
    { /* SM0: MBoxOut */
        46, /* minSize */
        522,
        1024,
        0x1000, /* startAddr */
        SM_TYPE_MBXOUT | REG_MASK_SMCONTROL_PDIINT /* contrByte */
    },

    { /* SM1: MBoxIn */
        46, /* minSize */
        522,
        1024,
        0x1400, /* startAddr */
        SM_TYPE_MBXIN | REG_MASK_SMCONTROL_PDIINT /* contrByte */
    },

    { /* SM2: Outputs */
        0, /* minSize */
        4, /* defSize */
        0, /* maxSize (0: unchecked) */
        0x1800, /* startAddr */
        SM_TYPE_OUTPUTS | REG_MASK_SMCONTROL_PDIINT /* contrByte */
    },

    { /* SM3: Inputs */
        0, /* minSize */
        4, /* defSize */
        0, /* maxSize (0: unchecked) */
        0x2400, /* startAddr */
        SM_TYPE_INPUTS | REG_MASK_SMCONTROL_PDIINT /* contrByte */
    }
};

/* Returns 0 when res == ESS_RESULT_SUCCESS. Also debug prints error when failed etc. */
static void CheckESSResult(ESS_RESULT res, int lineNo)
{
    if (res != ESS_RESULT_SUCCESS) {
        printf("ERROR: ess call in line %d failed with '%s'", lineNo, essFormatResult(res));
    }
}

/* Called by cbCyclic when in OP or SAFEOP */
static void updateInputs(void)
{
    static uint32_t nextUpdate;

    if (essGetTime() >= nextUpdate) {
        input1++;
        printf("*** Set input1 to %u \n", input1);

        /* To let the stack copy the entry data to the SM buffer: */
        essSyncInputs(hDev, ESS_ISYN_FLAGS_NONE);

        nextUpdate = essGetTime() + 1000;
    }
}

/* Called after output SM buffers were written by the master and stack copied that data to our variables (In Op state) */
static void cbOutputsUpdated(ESS_CBDATA_SM_EVENT* cbData)
{
    static uint32_t output1Prev;
    (void)cbData;

    if(cbData->sm & ESS_SM_EVENT_FLAG_SAFE_OUTPUTS) {
        /* Outputs in safe state */
    } else {
        if (output1 != output1Prev) {
            printf("*** output1 changed to %u \n", output1);
            output1Prev = output1;
        }
    }
}

static void cbCyclic(ESS_CBDATA_CYCLIC* cbData)
{
    (void)cbData;

    if ((currESCState == ESC_STATE_SAFEOP) || (currESCState == ESC_STATE_OP))
        updateInputs();

    //quit application after running several seconds.
    if (cbData->essTime - t > 1000*1000)
    {
        printf("essStop() ... \n");
        res = essStop(hDev);
        if (res != ESS_RESULT_SUCCESS)
        {
            printf("essStop() returned %s", essFormatResult(res));
        }
    }
}

static void cbStateRequest(ESS_CBDATA_STATE_REQUEST* cbData)
{
    const ESS_BOOL stackFoundNoError = (cbData->statusCode == REG_VAL_ALSTATUSCODE_NOERROR);

    printf("---> Transition from %s (0x%.2x) to %s (0x%.2x):", ESC_STATE_TO_STRING(LO_BYTE(cbData->transition)), LO_BYTE(cbData->transition), ESC_STATE_TO_STRING(HI_BYTE(cbData->transition)), HI_BYTE(cbData->transition));

    if (!stackFoundNoError) {
        printf("     Error %s (0x%.4x)", REG_VAL_ALSTATUSCODE_TO_STRING(cbData->statusCode), cbData->statusCode);

        /* When stack already found an error we MUST NOT override cbData->statusCode in this callback */
    }

    printf("---> New state will be %s (0x%.2x) \n", ESC_STATE_TO_STRING(cbData->newState), cbData->newState);
    
    currESCState = cbData->newState;

    if (currESCState == ESC_STATE_INIT)
    {   
        res = essODUpdatePDOConfiguration(hDev, 0x1600, "RxPDO1", PDO1600Entries, sizeof(PDO1600Entries)/sizeof(PDO1600Entries[0]), ESS_FALSE);
        if (res != ESS_RESULT_SUCCESS)
            printf("PDO1600Entries returned %s \n", essFormatResult(res));

        res = essODUpdatePDOConfiguration(hDev, 0x1a00, "TxPDO1", PDO1a00Entries, sizeof(PDO1a00Entries)/sizeof(PDO1a00Entries[0]), ESS_FALSE);
        if (res != ESS_RESULT_SUCCESS)
            printf("PDO1a00Entries returned %s \n", essFormatResult(res));

        res = essODUpdatePDOAssignment(hDev, ESS_SM_2, SM2DefPDOs, sizeof(SM2DefPDOs)/sizeof(SM2DefPDOs[0]), ESS_TRUE);
        if (res != ESS_RESULT_SUCCESS)
            printf("SM2DefPDOs returned %s \n", essFormatResult(res));
        
        res = essODUpdatePDOAssignment(hDev, ESS_SM_3, SM3DefPDOs, sizeof(SM3DefPDOs)/sizeof(SM3DefPDOs[0]), ESS_TRUE);
        if (res != ESS_RESULT_SUCCESS)
            printf("SM3DefPDOs returned %s \n", essFormatResult(res));
    }
}

static void cbInOutputsActivate(ESS_CBDATA_INOUTPUTS_ACTIVATE* cbData)
{    
    if (cbData->started) {

        if (cbData->inputs) 
        {
            printf("Start ESC In/Out process data update...\n");
        }else 
        {
            printf("Start local output update...\n");
        }
    }else {
        /* Inputs or outputs shall be stopped: */
        if (cbData->inputs) {
            printf("Stop ESC In/Out process data update...\n");
        } else {
            printf("Stop local output update...\n");
        }
    }
}

static void cbDCEvent(ESS_CBDATA_DC* cbData)
{}


static const ESS_CALLBACKS callbacks = {
    /* ESS_CB_CYCLIC */
    &cbCyclic,

    /* ESS_CB_STATE_REQUEST */
    &cbStateRequest,
    
    /* ESS_CB_SYNCMANAGER */
    NULL,

    /* ESS_CB_INOUTPUTS_ACTIVATE */
    &cbInOutputsActivate,
    
    /* ESS_CB_OUTPUTS_UPDATED */
    &cbOutputsUpdated,

    /* ESS_CB_INPUTS_UPDATED */
    NULL,

    /* ESS_CB_COE_READWRITE */
    NULL,

    /* ESS_CB_COE_EVENT */
    NULL,

    /* ESS_CB_FOE_OPEN */
    NULL,

    /* ESS_CB_FOE_CLOSE */
    NULL,
    
    /* ESS_CB_FOE_DATA */
    NULL,

    /* ESS_CB_EOE_SETIPPARAM */
    NULL,

    /* ESS_CB_EOE_SETADDRFILTER */
    NULL,

    /* ESS_CB_EOE_FRAME */
    NULL,

    /* ESS_CB_VOE */
    NULL,

    /* ESS_CB_EEPROM_EMULATION */
    NULL,

    /* ESS_CB_DC */
    &cbDCEvent,

    /* ESS_CB_SOE */
    NULL,
    
    /* ESS_CB_AOE */
    NULL,
};



/* Needed to initialize the stack with essOpen(): */
static const ESS_CONFIGURATION config = {
    ESS_ABI_VERSION,

    /* Config flags */
    ESS_CONFIG_FLAGS_USE_ISR,

    /* Timer interval for cyclic stack call / callback. In microseconds (Accuracy, etc. depends on hardware/platform. essStart() might also return ESS_RESULT_INVALID_TIMER_CONFIG when this value is too high or too low) */
    100 * 1000, /* us */

    /* Custom Tag, for later retrieval by essGetTag() */
    NULL,

    /* All stack callbacks */
    &callbacks,

    /* See smConfigs[] */
    smConfigs,
    (uint32_t) (sizeof(smConfigs) / sizeof((smConfigs)[0])),
    0,

    /* Reserved, must be 0 */
    { 0, 0 },

    /* Statistics */
    &essStats,

    /* Reserved */
    NULL,
};


int main(int argc, char *argv[])
{
    printf("essOpen() ...\n");
    res = essOpen(0, &config, &hDev);
    if (res != ESS_RESULT_SUCCESS)
        printf("essOpen() returned %s \n", essFormatResult(res));

    res = essODCreate(hDev, ESS_OD_FLAGS_HANDLE_SM_TYPES); 
    if (res != ESS_RESULT_SUCCESS)
        printf("essODCreate() returned %s \n", essFormatResult(res));

    /* Object 0x2000: "Output1", RX-Mappable (Output, mapped by default), read/write */
    {
        static const ESS_OD_OBJECT_INFOS objInfos = { "Output1", COE_DATATYPE_UDINT, COE_CODE_VARIABLE };
        static const ESS_OD_ENTRY_INFOS entryInfos0 = { "Output1", NULL, NULL, NULL, 0, COE_DATATYPE_UDINT };

        res = essODObjectAdd(hDev, 0x2000, ESS_OD_OBJECT_FLAGS_NONE, &objInfos); CHECK_RES();
        res = essODEntryAdd(hDev, 0x2000, 0x00, 32, &output1, COE_ACCESS_RXMAPPABLE | COE_ACCESS_RW, ESS_OD_ENTRY_FLAGS_NONE, &entryInfos0); CHECK_RES();
    }
    
    /* Object 0x2010: "Input1", TX-Mappable (Input, mapped by default), read/write */
    {
        static const ESS_OD_OBJECT_INFOS objInfos = { "Input1", COE_DATATYPE_UDINT, COE_CODE_VARIABLE };
        static const ESS_OD_ENTRY_INFOS entryInfos0 = { "Input1", NULL, NULL, NULL, 0, COE_DATATYPE_UDINT };

        res = essODObjectAdd(hDev, 0x2010, ESS_OD_OBJECT_FLAGS_NONE, &objInfos); CHECK_RES();
        res = essODEntryAdd(hDev, 0x2010, 0x00, 32, &input1, COE_ACCESS_TXMAPPABLE | COE_ACCESS_RW, ESS_OD_ENTRY_FLAGS_NONE, &entryInfos0); CHECK_RES();
    }
    
    t = essGetTime();
    printf("essStart() ... \n");
    res = essStart(hDev);
    if (res != ESS_RESULT_SUCCESS)
        printf("essStart() returned %s", essFormatResult(res));

    essClose(hDev);
    return 0;

}


