/** \file
 * \brief Example code for Simple Open EtherCAT master
 *
 * This is a minimal example running rt-kernel TTOS. Check
 * tutorial files in documentations for som steps to get your
 * system running.
 * (c)Andreas Karlsson 2012
 */

#include <kern.h>
#include "ethercat.h"
#include "string.h"
#include <oshw.h>
#include <config.h>

#include <defBF537.h>

#define pPORTFIO_SET  ((vuint16_t *)PORTFIO_SET)
#define pPORTFIO_CLEAR  ((vuint16_t *)PORTFIO_CLEAR)
#define pPORTFIO_DIR  ((vuint16_t *)PORTFIO_DIR)

#define EK1100_1           1
#define EL4001_1           2
#define EL3061_1           3
#define EL1008_1           4
#define EL1008_2           5
#define EL2622_1           6
#define EL2622_2           7
#define EL2622_3           8
#define EL2622_4           9
#define NUMBER_OF_SLAVES   9


typedef struct
{
   uint8    in1;
   uint8    in2;
   uint8    in3;
   uint8    in4;
   uint8    in5;
   uint8    in6;
   uint8    in7;
   uint8    in8;
} in_EL1008_t;

typedef struct
{
   uint8    out1;
   uint8    out2;
} out_EL2622_t;

typedef struct
{
   int16    out1;
} out_EL4001_t;

typedef struct
{
   int32    in1;
} in_EL3061_t;

out_EL4001_t   slave_EL4001_1;
in_EL3061_t    slave_EL3061_1;
in_EL1008_t    slave_EL1008_1;
in_EL1008_t    slave_EL1008_2;
out_EL2622_t   slave_EL2622_1;
out_EL2622_t   slave_EL2622_2;
out_EL2622_t   slave_EL2622_3;

uint32 network_configuration(void)
{
   /* Do we got expected number of slaves from config */
   if (ec_slavecount < NUMBER_OF_SLAVES)
      return 0;

   /* Verify slave by slave that it is correct*/
   if (strcmp(ec_slave[EK1100_1].name,"EK1100"))
      return 0;
   else if (strcmp(ec_slave[EL4001_1].name,"EL4001"))
      return 0;
   else if (strcmp(ec_slave[EL3061_1].name,"EL3061"))
      return 0;
   else if (strcmp(ec_slave[EL1008_1].name,"EL1008"))
      return 0;
   else if (strcmp(ec_slave[EL1008_2].name,"EL1008"))
      return 0;
   else if (strcmp(ec_slave[EL2622_1].name,"EL2622"))
      return 0;
   else if (strcmp(ec_slave[EL2622_2].name,"EL2622"))
      return 0;
   else if (strcmp(ec_slave[EL2622_3].name,"EL2622"))
      return 0;
   else if (strcmp(ec_slave[EL2622_4].name,"EL2622"))
      return 0;

  return 1;
}

int32 get_input_int32(uint16 slave_no,uint8 module_index)
{
   int32 return_value;
   uint8 *data_ptr;
   /* Get the IO map pointer from the ec_slave struct */
   data_ptr = ec_slave[slave_no].inputs;
   /* Move pointer to correct module index */
   data_ptr += module_index * 4;
   /* Read value byte by byte since all targets can't handle misaligned
    * addresses
    */
   return_value = *data_ptr++;
   return_value += (*data_ptr++ << 8);
   return_value += (*data_ptr++ << 16);
   return_value += (*data_ptr++ << 24);

   return return_value;
}

void set_input_int32 (uint16 slave_no, uint8 module_index, int32 value)
{
   uint8 *data_ptr;
   /* Get the IO map pointer from the ec_slave struct */
   data_ptr = ec_slave[slave_no].inputs;
   /* Move pointer to correct module index */
   data_ptr += module_index * 4;
   /* Read value byte by byte since all targets can handle misaligned
    * addresses
    */
   *data_ptr++ = (value >> 0) & 0xFF;
   *data_ptr++ = (value >> 8) & 0xFF;
   *data_ptr++ = (value >> 16) & 0xFF;
   *data_ptr++ = (value >> 24) & 0xFF;
}

uint8 get_input_bit (uint16 slave_no,uint8 module_index)
{
   /* Get the the startbit position in slaves IO byte */
   uint8 startbit = ec_slave[slave_no].Istartbit;
   /* Mask bit and return boolean 0 or 1 */
   if (*ec_slave[slave_no].inputs & BIT (module_index - 1  + startbit))
      return 1;
   else
      return 0;
}

int16 get_output_int16(uint16 slave_no,uint8 module_index)
{
   int16 return_value;
   uint8 *data_ptr;

   /* Get the IO map pointer from the ec_slave struct */
   data_ptr = ec_slave[slave_no].outputs;
   /* Move pointer to correct module index */
   data_ptr += module_index * 2;
   /* Read value byte by byte since all targets can handle misaligned
    * addresses
    */
   return_value = *data_ptr++;
   return_value += (*data_ptr++ << 8);

   return return_value;
}

void set_output_int16 (uint16 slave_no, uint8 module_index, int16 value)
{
   uint8 *data_ptr;
   /* Get the IO map pointer from the ec_slave struct */
   data_ptr = ec_slave[slave_no].outputs;
   /* Move pointer to correct module index */
   data_ptr += module_index * 2;
   /* Read value byte by byte since all targets can handle misaligned
    * addresses
    */
   *data_ptr++ = (value >> 0) & 0xFF;
   *data_ptr++ = (value >> 8) & 0xFF;
}

uint8 get_output_bit (uint16 slave_no,uint8 module_index)
{
   /* Get the the startbit position in slaves IO byte */
   uint8 startbit = ec_slave[slave_no].Ostartbit;
   /* Mask bit and return boolean 0 or 1 */
   if (*ec_slave[slave_no].outputs & BIT (module_index - 1  + startbit))
      return 1;
   else
      return 0;
}

void set_output_bit (uint16 slave_no, uint8 module_index, uint8 value)
{
   /* Get the the startbit position in slaves IO byte */
   uint8 startbit = ec_slave[slave_no].Ostartbit;
   /* Set or Clear bit */
   if (value == 0)
      *ec_slave[slave_no].outputs &= ~(1 << (module_index - 1 + startbit));
   else
      *ec_slave[slave_no].outputs |= (1 << (module_index - 1 + startbit));
}


extern tt_sched_t * tt_sched[];

char IOmap[128];
int dorun = 0;

uint8_t load1s, load5s, load10s;
uint32_t error_counter = 0;

void tt_error (uint32_t task_ix);
void tt_error (uint32_t task_ix)
{
   error_counter++;
}

static void my_cyclic_callback (void * arg)
{
   while (1)
   {
      stats_get_load (&load1s, &load5s, &load10s);
      rprintp ("Processor load was %d:%d:%d (1s:5s:10s) with TT errors: %d\n",
               load1s, load5s, load10s,error_counter);
      task_delay(20000);
   }
}


void read_io (void * arg)
{
   /* Function connected to cyclic TTOS task
    * The function is executed cyclic according
    * sceduel specified in schedule.tt
    */
   *pPORTFIO_SET = BIT (6);
   /* Send and receive processdata
    * Note that you need som synchronization
    * case you modifey IO in local application
    */
   ec_send_processdata();
   ec_receive_processdata(EC_TIMEOUTRET);
   *pPORTFIO_CLEAR = BIT (6);
}


void simpletest(void *arg)
{

   char *ifname = arg;
   int cnt, i, j;

   *pPORTFIO_DIR |= BIT (6);

   rprintp("Starting simple test\n");

   /* initialise SOEM */
   if (ec_init(ifname))
   {
      rprintp("ec_init succeeded.\n");

      /* find and auto-config slaves */
      if ( ec_config_init(FALSE) > 0 )
      {
         rprintp("%d slaves found and configured.\n",ec_slavecount);

         /* Check network  setup */
         if (network_configuration())
         {
            /* Run IO mapping */
            ec_config_map(&IOmap);

            rprintp("Slaves mapped, state to SAFE_OP.\n");
            /* wait for all slaves to reach SAFE_OP state */
            ec_statecheck(0, EC_STATE_SAFE_OP,  EC_TIMEOUTSTATE);

            /* Print som information on the mapped network */
            for( cnt = 1 ; cnt <= ec_slavecount ; cnt++)
            {
               rprintp("\nSlave:%d\n Name:%s\n Output size: %dbits\n Input size: %dbits\n State: %d\n Delay: %d[ns]\n Has DC: %d\n",
                       cnt, ec_slave[cnt].name, ec_slave[cnt].Obits, ec_slave[cnt].Ibits,
                       ec_slave[cnt].state, ec_slave[cnt].pdelay, ec_slave[cnt].hasdc);
               rprintp(" Configured address: %x\n", ec_slave[cnt].configadr);
               rprintp(" Outputs address: %x\n", ec_slave[cnt].outputs);
               rprintp(" Inputs address: %x\n", ec_slave[cnt].inputs);

               for(j = 0 ; j < ec_slave[cnt].FMMUunused ; j++)
               {
                  rprintp(" FMMU%1d Ls:%x Ll:%4d Lsb:%d Leb:%d Ps:%x Psb:%d Ty:%x Act:%x\n", j,
                          (int)ec_slave[cnt].FMMU[j].LogStart, ec_slave[cnt].FMMU[j].LogLength, ec_slave[cnt].FMMU[j].LogStartbit,
                          ec_slave[cnt].FMMU[j].LogEndbit, ec_slave[cnt].FMMU[j].PhysStart, ec_slave[cnt].FMMU[j].PhysStartBit,
                          ec_slave[cnt].FMMU[j].FMMUtype, ec_slave[cnt].FMMU[j].FMMUactive);
               }
               rprintp(" FMMUfunc 0:%d 1:%d 2:%d 3:%d\n",
                        ec_slave[cnt].FMMU0func, ec_slave[cnt].FMMU1func, ec_slave[cnt].FMMU2func, ec_slave[cnt].FMMU3func);

            }

            rprintp("Request operational state for all slaves\n");
            ec_slave[0].state = EC_STATE_OPERATIONAL;
            /* send one valid process data to make outputs in slaves happy*/
            ec_send_processdata();
            ec_receive_processdata(EC_TIMEOUTRET);
            /* request OP state for all slaves */
            ec_writestate(0);
            /* wait for all slaves to reach OP state */
            ec_statecheck(0, EC_STATE_OPERATIONAL,  EC_TIMEOUTSTATE);
            if (ec_slave[0].state == EC_STATE_OPERATIONAL )
            {
               rprintp("Operational state reached for all slaves.\n");
            }
            else
            {
               rprintp("Not all slaves reached operational state.\n");
               ec_readstate();
               for(i = 1; i<=ec_slavecount ; i++)
               {
                  if(ec_slave[i].state != EC_STATE_OPERATIONAL)
                  {
                     rprintp("Slave %d State=0x%04x StatusCode=0x%04x\n",
                             i, ec_slave[i].state, ec_slave[i].ALstatuscode);
                  }
               }
            }


            /* Simple blinking lamps BOX demo */
            uint8 digout = 0;

            slave_EL4001_1.out1 = (int16)0x3FFF;
            set_output_int16(EL4001_1,0,slave_EL4001_1.out1);

            task_spawn ("t_StatsPrint", my_cyclic_callback, 20, 1024, (void *)NULL);
            tt_start_wait (tt_sched[0]);

            while(1)
            {
               dorun = 0;
               slave_EL1008_1.in1 = get_input_bit(EL1008_1,1); // Start button
               slave_EL1008_1.in2 = get_input_bit(EL1008_1,2);  // Turnkey RIGHT
               slave_EL1008_1.in3 = get_input_bit(EL1008_1,3); // Turnkey LEFT

               /* (Turnkey MIDDLE + Start button) OR Turnkey RIGHT OR Turnkey LEFT
                  Turnkey LEFT: Light positions bottom to top. Loop, slow operation.
                  Turnkey MIDDLE: Press start button to light positions bottom to top. No loop, fast operation.
                  Turnkey RIGHT: Light positions bottom to top. Loop, fast operation.
               */
               if (slave_EL1008_1.in1 || slave_EL1008_1.in2 || slave_EL1008_1.in3)
               {
                  digout = 0;
                  /* *ec_slave[6].outputs = digout; */
                  /* set_output_bit(slave_name #,index as 1 output on module , value */
                  set_output_bit(EL2622_1,1,(digout & BIT (0))); /* Start button */
                  set_output_bit(EL2622_1,2,(digout & BIT (1))); /* Turnkey RIGHT */
                  set_output_bit(EL2622_2,1,(digout & BIT (2))); /* Turnkey LEFT */
                  set_output_bit(EL2622_2,2,(digout & BIT (3)));
                  set_output_bit(EL2622_3,1,(digout & BIT (4)));
                  set_output_bit(EL2622_3,2,(digout & BIT (5)));

                  while(dorun < 95)
                  {
                     dorun++;

                     if (slave_EL1008_1.in3)
                        task_delay(tick_from_ms(20));
                     else
                        task_delay(tick_from_ms(5));

                     digout = (uint8) (digout | BIT((dorun / 16) & 0xFF));

                     set_output_bit(EL2622_1,1,(digout & BIT (0))); /* LED1 */
                     set_output_bit(EL2622_1,2,(digout & BIT (1))); /* LED2 */
                     set_output_bit(EL2622_2,1,(digout & BIT (2))); /* LED3 */
                     set_output_bit(EL2622_2,2,(digout & BIT (3))); /* LED4 */
                     set_output_bit(EL2622_3,1,(digout & BIT (4))); /* LED5 */
                     set_output_bit(EL2622_3,2,(digout & BIT (5))); /* LED6 */

                     slave_EL1008_1.in1 = get_input_bit(EL1008_1,2);  /* Turnkey RIGHT */
                     slave_EL1008_1.in2 = get_input_bit(EL1008_1,3); /* Turnkey LEFT */
                     slave_EL3061_1.in1 = get_input_int32(EL3061_1,0); /* Read AI */
                  }
               }
               task_delay(tick_from_ms(2));

            }
         }
         else
         {
            rprintp("Mismatch of network units!\n");
         }
      }
      else
      {
         rprintp("No slaves found!\n");
      }
      rprintp("End simple test, close socket\n");
      /* stop SOEM, close socket */
      ec_close();
   }
   else
   {
      rprintp("ec_init failed");
   }
   rprintp("End program\n");
}

static void test_osal_timer_timeout_us (const uint32 timeout_us)
{
   osal_timert timer;

   ASSERT (timeout_us > 4000);

   osal_timer_start (&timer, timeout_us);
   ASSERT (osal_timer_is_expired (&timer) == false);
   osal_usleep (timeout_us - 2000);
   ASSERT (osal_timer_is_expired (&timer) == false);
   osal_usleep (4000);
   ASSERT (osal_timer_is_expired (&timer));
}

static void test_osal_timer (void)
{
   test_osal_timer_timeout_us (10*1000);     /* 10ms */
   test_osal_timer_timeout_us (100*1000);    /* 100ms */
   test_osal_timer_timeout_us (1000*1000);   /* 1s */
   test_osal_timer_timeout_us (2000*1000);   /* 2s */
}

#define USECS_PER_SEC   1000000
#define USECS_PER_TICK  (USECS_PER_SEC / CFG_TICKS_PER_SECOND)
#ifndef ABS
#define ABS(x) ((x) < 0 ? -(x) : (x))
#endif

static int32 time_difference_us (const ec_timet stop, const ec_timet start)
{
   int32 difference_us;

   ASSERT (stop.sec >= start.sec);
   if (stop.sec == start.sec)
   {
      ASSERT (stop.usec >= start.usec);
   }

   difference_us = (stop.sec - start.sec) * USECS_PER_SEC;
   difference_us += ((int32)stop.usec - (int32)start.usec);

   ASSERT (difference_us >= 0);
   return difference_us;
}

/**
 * Test osal_current_time() by using it for measuring how long an osal_usleep()
 * takes, in specified number of microseconds.
 */
static void test_osal_current_time_for_delay_us (const int32 sleep_time_us)
{
   ec_timet start;
   ec_timet stop;
   int32 measurement_us;
   int32 deviation_us;
   const int32 usleep_accuracy_us = USECS_PER_TICK;
   boolean is_deviation_within_tolerance;

   start = osal_current_time ();
   osal_usleep (sleep_time_us);
   stop = osal_current_time ();

   measurement_us = time_difference_us (stop, start);
   deviation_us = ABS (measurement_us - sleep_time_us);
   is_deviation_within_tolerance = deviation_us <= usleep_accuracy_us;
   ASSERT (is_deviation_within_tolerance);
}

static void test_osal_current_time (void)
{
   test_osal_current_time_for_delay_us (0);
   test_osal_current_time_for_delay_us (1);
   test_osal_current_time_for_delay_us (500);
   test_osal_current_time_for_delay_us (999);
   test_osal_current_time_for_delay_us (USECS_PER_TICK);
   test_osal_current_time_for_delay_us (USECS_PER_TICK-1);
   test_osal_current_time_for_delay_us (USECS_PER_TICK+1);
   test_osal_current_time_for_delay_us (2 * 1000 * 1000);  /* 2s */
}

#include <lwip/inet.h>

static void test_oshw_htons (void)
{
	   uint16 network;
	   uint16 host;

	   host = 0x1234;
	   network = oshw_htons (host);
	   ASSERT (network == htons (host));
}

static void test_oshw_ntohs (void)
{
	   uint16 host;
	   uint16 network;

	   network = 0x1234;
	   host = oshw_ntohs (network);
	   ASSERT (host == ntohs (network));
}

int main (void)
{
   test_oshw_htons ();
   test_oshw_ntohs ();
   test_osal_timer ();
   test_osal_current_time ();

   rprintp("SOEM (Simple Open EtherCAT Master)\nSimple test\n");

   task_spawn ("simpletest", simpletest, 9, 8192, NULL);

   return (0);
}

