/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    app_netxduo.c
  * @author  MCD Application Team
  * @brief   NetXDuo applicative file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2020-2021 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "app_netxduo.h"

/* Private includes ----------------------------------------------------------*/
#include "nxd_dhcp_client.h"
/* USER CODE BEGIN Includes */
#include "main.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
TX_THREAD      NxAppThread;
NX_PACKET_POOL NxAppPool;
NX_IP          NetXDuoEthIpInstance;
TX_SEMAPHORE   DHCPSemaphore;
NX_DHCP        DHCPClient;
/* USER CODE BEGIN PV */

TX_THREAD       AppLinkThread;
ULONG           IpAddress;
ULONG           NetMask;
NX_DHCP         DHCPClient;

// TCPIP COM PORT
TX_SEMAPHORE    TCPSemaphore;
TX_THREAD       AppTCPThread;
TX_THREAD       AppTCPTransmitThread;
NX_TCP_SOCKET   TCPSocket;

UCHAR           com_port_receive_data_buffer[DEFAULT_PAYLOAD_SIZE];
signed          com_port_receive_data_length;
signed          com_port_receive_data_offset;
UCHAR           com_port_transmit_data_buffer[DEFAULT_PAYLOAD_SIZE];
signed          com_port_transmit_data_length;
uint8_t         com_port_transmitting;

TX_EVENT_FLAGS_GROUP  ComPortReceiveEventFlags;
TX_EVENT_FLAGS_GROUP  ComPortTransmitEventFlags;

// MODBUS TCP
TX_SEMAPHORE    ModbusSemaphore;
TX_THREAD       AppModbusThread;
TX_THREAD       AppModbusTransmitThread;
NX_TCP_SOCKET   ModbusSocket;

UCHAR           modbus_receive_data_buffer[DEFAULT_PAYLOAD_SIZE];
signed          modbus_receive_data_length;
signed          modbus_receive_data_offset;
UCHAR           modbus_transmit_data_buffer[DEFAULT_PAYLOAD_SIZE];
signed          modbus_transmit_data_length;
uint8_t         modbus_transmitting;

TX_EVENT_FLAGS_GROUP  ModbusReceiveEventFlags;
TX_EVENT_FLAGS_GROUP  ModbusTransmitEventFlags;

extern uint8_t      TCPIP_Enabled;
extern uint8_t      TCPIP_Cable_Connected;
extern uint8_t      TCPIP_IP_Address_Assigned;
extern uint32_t     TCPIP_NetAddress;
extern uint32_t     TCPIP_NetMask;
extern uint8_t      TCPIP_ComPort_Connected;
extern uint8_t      TCPIP_ModbusTCP_Connected;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
static VOID nx_app_thread_entry (ULONG thread_input);
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);
/* USER CODE BEGIN PFP */
static VOID App_TCP_Thread_Entry(ULONG thread_input);
static VOID App_TCP_Transmit_Thread_Entry(ULONG thread_input);
static VOID App_Modbus_Thread_Entry(ULONG thread_input);
static VOID App_Modbus_Transmit_Thread_Entry(ULONG thread_input);
static VOID App_Link_Thread_Entry(ULONG thread_input);
static VOID tcp_listen_callback(NX_TCP_SOCKET *socket_ptr, UINT port);
/* USER CODE END PFP */

/**
  * @brief  Application NetXDuo Initialization.
  * @param memory_ptr: memory pointer
  * @retval int
  */
UINT MX_NetXDuo_Init(VOID *memory_ptr)
{
  UINT ret = NX_SUCCESS;
  TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
  CHAR *pointer;

  /* USER CODE BEGIN MX_NetXDuo_MEM_POOL */

  /* USER CODE END MX_NetXDuo_MEM_POOL */

  /* USER CODE BEGIN 0 */
//  printf("Nx_TCP_Server application started..\n");
  /* USER CODE END 0 */

  /* Initialize the NetXDuo system. */
  nx_system_initialize();

    /* Allocate the memory for packet_pool.  */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_PACKET_POOL_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* Create the Packet pool to be used for packet allocation,
   * If extra NX_PACKET are to be used the NX_APP_PACKET_POOL_SIZE should be increased
   */
  ret = nx_packet_pool_create(&NxAppPool, "NetXDuo App Pool", DEFAULT_PAYLOAD_SIZE, pointer, NX_APP_PACKET_POOL_SIZE);

  if (ret != NX_SUCCESS)
  {
    return NX_POOL_ERROR;
  }

    /* Allocate the memory for Ip_Instance */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, Nx_IP_INSTANCE_THREAD_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

   /* Create the main NX_IP instance */
  ret = nx_ip_create(&NetXDuoEthIpInstance, "NetX Ip instance", NX_APP_DEFAULT_IP_ADDRESS, NX_APP_DEFAULT_NET_MASK, &NxAppPool, nx_stm32_eth_driver,
                     pointer, Nx_IP_INSTANCE_THREAD_SIZE, NX_APP_INSTANCE_PRIORITY);

  if (ret != NX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

    /* Allocate the memory for ARP */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, DEFAULT_ARP_CACHE_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* Enable the ARP protocol and provide the ARP cache size for the IP instance */

  /* USER CODE BEGIN ARP_Protocol_Initialization */

  /* USER CODE END ARP_Protocol_Initialization */

  ret = nx_arp_enable(&NetXDuoEthIpInstance, (VOID *)pointer, DEFAULT_ARP_CACHE_SIZE);

  if (ret != NX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  /* Enable the ICMP */

  /* USER CODE BEGIN ICMP_Protocol_Initialization */

  /* USER CODE END ICMP_Protocol_Initialization */

  ret = nx_icmp_enable(&NetXDuoEthIpInstance);

  if (ret != NX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  /* Enable TCP Protocol */

  /* USER CODE BEGIN TCP_Protocol_Initialization */

  if (tx_event_flags_create(&ComPortReceiveEventFlags, "ComPortReceiveEventFlags") != TX_SUCCESS)
  {
    Error_Handler();
  }

  if (tx_event_flags_create(&ComPortTransmitEventFlags, "ComPortTransmitEventFlags") != TX_SUCCESS)
  {
    Error_Handler();
  }

  /* Allocate the memory for TCP/IP com port server thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the TCP/IP com port server thread */
  ret = tx_thread_create(&AppTCPThread, "App TCP Thread", App_TCP_Thread_Entry, 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         NX_APP_THREAD_PRIORITY, NX_APP_THREAD_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START);

  if (ret != TX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  /* Allocate the memory for TCP transmit server thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the TCP transmit server thread */
  ret = tx_thread_create(&AppTCPTransmitThread, "App TCP Transmit Thread", App_TCP_Transmit_Thread_Entry, 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         NX_APP_THREAD_PRIORITY, NX_APP_THREAD_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START);

  if (ret != TX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  if (tx_event_flags_create(&ModbusReceiveEventFlags, "ModbusReceiveEventFlags") != TX_SUCCESS)
  {
    Error_Handler();
  }

  if (tx_event_flags_create(&ModbusTransmitEventFlags, "ModbusTransmitEventFlags") != TX_SUCCESS)
  {
    Error_Handler();
  }

  /* Allocate the memory for Modbus server thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the Modbus server thread */
  ret = tx_thread_create(&AppModbusThread, "App Modbus Thread", App_Modbus_Thread_Entry, 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         NX_APP_THREAD_PRIORITY, NX_APP_THREAD_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START);

  if (ret != TX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  /* Allocate the memory for Modbus transmit server thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the Modbus transmit server thread */
  ret = tx_thread_create(&AppModbusTransmitThread, "App Modbus Transmit Thread", App_Modbus_Transmit_Thread_Entry, 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         NX_APP_THREAD_PRIORITY, NX_APP_THREAD_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START);

  if (ret != TX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }
  /* USER CODE END TCP_Protocol_Initialization */

  ret = nx_tcp_enable(&NetXDuoEthIpInstance);

  if (ret != NX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

  /* Enable the UDP protocol required for  DHCP communication */

  /* USER CODE BEGIN UDP_Protocol_Initialization */

  /* USER CODE END UDP_Protocol_Initialization */

  ret = nx_udp_enable(&NetXDuoEthIpInstance);

  if (ret != NX_SUCCESS)
  {
    return NX_NOT_SUCCESSFUL;
  }

   /* Allocate the memory for main thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* Create the main thread */
  ret = tx_thread_create(&NxAppThread, "NetXDuo App thread", nx_app_thread_entry , 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         NX_APP_THREAD_PRIORITY, NX_APP_THREAD_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);

  if (ret != TX_SUCCESS)
  {
    return TX_THREAD_ERROR;
  }

  /* Create the DHCP client */

  /* USER CODE BEGIN DHCP_Protocol_Initialization */

  /* USER CODE END DHCP_Protocol_Initialization */

  ret = nx_dhcp_create(&DHCPClient, &NetXDuoEthIpInstance, "DHCP Client");

  if (ret != NX_SUCCESS)
  {
    return NX_DHCP_ERROR;
  }

  /* set DHCP notification callback  */
  tx_semaphore_create(&DHCPSemaphore, "DHCP Semaphore", 0);

  /* USER CODE BEGIN MX_NetXDuo_Init */
  /* set DHCP notification callback  */
  tx_semaphore_create(&TCPSemaphore, "TCP Semaphore", 0);
  tx_semaphore_create(&ModbusSemaphore, "Modbus Semaphore", 0);

  /* Allocate the memory for Link thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer,NX_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the Link thread */
  ret = tx_thread_create(&AppLinkThread, "App Link Thread", App_Link_Thread_Entry, 0, pointer, NX_APP_THREAD_STACK_SIZE,
                         LINK_PRIORITY, LINK_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);

  if (ret != TX_SUCCESS)
  {
    return NX_NOT_ENABLED;
  }

//  printf("Nx_TCP_Server application startup complete\n");
  TCPIP_Enabled = 1;

  /* USER CODE END MX_NetXDuo_Init */

  return ret;
}

/**
* @brief  ip address change callback.
* @param ip_instance: NX_IP instance
* @param ptr: user data
* @retval none
*/
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr)
{
  /* USER CODE BEGIN ip_address_change_notify_callback */
  /* release the semaphore as soon as an IP address is available */
  if (nx_ip_address_get(&NetXDuoEthIpInstance, &IpAddress, &NetMask) != NX_SUCCESS)
  {
    /* USER CODE BEGIN IP address change callback error */
    Error_Handler();
    /* USER CODE END IP address change callback error */
  }
  if(IpAddress != NULL_ADDRESS)
  {
    tx_semaphore_put(&DHCPSemaphore);
  }
  /* USER CODE END ip_address_change_notify_callback */
}

/**
* @brief  Main thread entry.
* @param thread_input: ULONG user argument used by the thread entry
* @retval none
*/
static VOID nx_app_thread_entry (ULONG thread_input)
{
  /* USER CODE BEGIN Nx_App_Thread_Entry 0 */

  /* USER CODE END Nx_App_Thread_Entry 0 */

  UINT ret = NX_SUCCESS;

  /* USER CODE BEGIN Nx_App_Thread_Entry 1 */

  /* USER CODE END Nx_App_Thread_Entry 1 */

  /* register the IP address change callback */
  ret = nx_ip_address_change_notify(&NetXDuoEthIpInstance, ip_address_change_notify_callback, NULL);
  if (ret != NX_SUCCESS)
  {
    /* USER CODE BEGIN IP address change callback error */
    Error_Handler();
    /* USER CODE END IP address change callback error */
  }

  /* start the DHCP client */
  ret = nx_dhcp_start(&DHCPClient);
  if (ret != NX_SUCCESS)
  {
    /* USER CODE BEGIN DHCP client start error */
    Error_Handler();
    /* USER CODE END DHCP client start error */
  }
  printf("Looking for DHCP server ..\n");
  /* wait until an IP address is ready */
  if(tx_semaphore_get(&DHCPSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS)
  {
    /* USER CODE BEGIN DHCPSemaphore get error */
//    printf("No DHCP server detected\n");
    /* USER CODE END DHCPSemaphore get error */
  }

  /* USER CODE BEGIN Nx_App_Thread_Entry 2 */
//  PRINT_IP_ADDRESS(IpAddress);
  TCPIP_IP_Address_Assigned = 1;
  TCPIP_NetAddress = IpAddress;
  TCPIP_NetMask = NetMask;
  {
    char buff[50];
    sprintf(buff, "IP ADDRESS %lu.%lu.%lu.%lu \n", (IpAddress >> 24) & 0xff, (IpAddress >> 16) & 0xff, (IpAddress >> 8) & 0xff, (IpAddress & 0xff));
    OutputDebug(buff);
  }

  /* the network is correctly initialized, start the TCP server thread */
  tx_thread_resume(&AppTCPThread);
  tx_thread_resume(&AppTCPTransmitThread);

  /* the network is correctly initialized, start the TCP server thread */
  tx_thread_resume(&AppModbusThread);
  tx_thread_resume(&AppModbusTransmitThread);

  /* if this thread is not needed any more, we relinquish it */
  tx_thread_relinquish();

  return;
  /* USER CODE END Nx_App_Thread_Entry 2 */

}
/* USER CODE BEGIN 1 */
/******************************************************************************************************************************************************************************************************/
/**
* @brief  TCP listen call back
* @param socket_ptr: NX_TCP_SOCKET socket registered for the callback
* @param port: UINT  the port on which the socket is listening
* @retval none
*/
static VOID tcp_listen_callback(NX_TCP_SOCKET *socket_ptr, UINT port)
{
  tx_semaphore_put(&TCPSemaphore);
}

/**
* @brief  TCP server thread entry
* @param thread_input: ULONG thread parameter
* @retval none
*/
static VOID App_TCP_Thread_Entry(ULONG thread_input)
{
  UINT ret;
  NX_PACKET *data_packet;
//  ULONG source_ip_address;
//  UINT source_port;
  ULONG bytes_read;
  ULONG length;
  ULONG actual_flags;

  /* create the TCP socket */
  ret = nx_tcp_socket_create(&NetXDuoEthIpInstance, &TCPSocket, "TCP Server Socket", NX_IP_NORMAL, NX_FRAGMENT_OKAY,
                             NX_IP_TIME_TO_LIVE, WINDOW_SIZE, NX_NULL, NX_NULL);
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /*
  * listen to new client connections.
  * the TCP_listen_callback will release the 'Semaphore' when a new connection is available
  */
  ret = nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, TCPIP_COM_PORT, &TCPSocket, MAX_TCP_CLIENTS, tcp_listen_callback);

  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
//    printf("TCP Server listening on PORT %d ..\n", TCPIP_COM_PORT);
  }

  if(tx_semaphore_get(&TCPSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
    /* accept the new client connection before starting data exchange */
    ret = nx_tcp_server_socket_accept(&TCPSocket, TX_WAIT_FOREVER);

    if (ret != NX_SUCCESS)
    {
      Error_Handler();
    }
    TCPIP_ComPort_Connected = 1;
  }

  while(1)
  {
    ULONG socket_state;

    TX_MEMSET(com_port_receive_data_buffer, '\0', sizeof(com_port_receive_data_buffer));

    /* get the socket state */
    nx_tcp_socket_info_get(&TCPSocket, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &socket_state, NULL, NULL, NULL);

    /* if the connections is not established then accept new ones, otherwise start receiving data */
    if(socket_state != NX_TCP_ESTABLISHED)
    {
      TCPIP_ComPort_Connected = 0;
      ret = nx_tcp_server_socket_accept(&TCPSocket, NX_IP_PERIODIC_RATE);
      TCPIP_ComPort_Connected = 1;
    }

    if(ret == NX_SUCCESS)
    {
      /* receive the TCP packet send by the client */
      ret = nx_tcp_socket_receive(&TCPSocket, &data_packet, NX_WAIT_FOREVER);

      if (ret == NX_SUCCESS)
      {
//        HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);

        /* get the client IP address and  port */
//        nx_udp_source_extract(data_packet, &source_ip_address, &source_port);

        /* print the received data */
//        PRINT_DATA(source_ip_address, source_port, com_port_receive_data_buffer);

        nx_packet_length_get(data_packet, &length);

        if (length <= sizeof(com_port_receive_data_buffer))
        {
          /* retrieve the data sent by the client */
          nx_packet_data_retrieve(data_packet, com_port_receive_data_buffer, &bytes_read);
          com_port_receive_data_offset = 0;
          com_port_receive_data_length = bytes_read;
          TCPIP_ReceiveReady();
          if (tx_event_flags_get(&ComPortReceiveEventFlags, 0x00000001, TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER) != TX_SUCCESS)
          {
            Error_Handler();
          }
        }

        nx_packet_release(data_packet);
      }
      else
      {
        nx_tcp_socket_disconnect(&TCPSocket, NX_WAIT_FOREVER);
        nx_tcp_server_socket_unaccept(&TCPSocket);
        nx_tcp_server_socket_relisten(&NetXDuoEthIpInstance, TCPIP_COM_PORT, &TCPSocket);
      }
    }
    else
    {
      /*toggle the green led to indicate the idle state */
//      HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);
      tx_thread_sleep(10);
    }
  }
}

static VOID App_TCP_Transmit_Thread_Entry(ULONG thread_input)
{
  while(1)
  {
    UINT ret;
    NX_PACKET *data_packet;

    ULONG actual_flags;

    if (tx_event_flags_get(&ComPortTransmitEventFlags, 0x00000001, TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER) != TX_SUCCESS)
    {
      Error_Handler();
    }

    if (com_port_transmit_data_length > 0)
    {
      nx_packet_allocate(&NxAppPool, &data_packet, NX_TCP_PACKET, NX_WAIT_FOREVER);
      nx_packet_data_append(data_packet, com_port_transmit_data_buffer, com_port_transmit_data_length, &NxAppPool, NX_WAIT_FOREVER);
      ret =  nx_tcp_socket_send(&TCPSocket, data_packet, NX_WAIT_FOREVER);
      if (ret == NX_SUCCESS)
      {
      }
//      HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);
    }
    com_port_transmit_data_length = 0;
    com_port_transmitting = 0;
    TCPIP_TransmitReady();
  }
}

signed TCPIP_Receive(void *Buff, signed Size)
{
  signed Count = com_port_receive_data_length - com_port_receive_data_offset;

  if (Count > Size) Count = Size;
  if (Count == 0) return 0;
  memcpy(Buff, &com_port_receive_data_buffer[com_port_receive_data_offset], Count);
  com_port_receive_data_offset += Count;
  if (com_port_receive_data_length == com_port_receive_data_offset)
  {
    if (tx_event_flags_set(&ComPortReceiveEventFlags, 0x00000001, TX_OR) != TX_SUCCESS)
    {
      Error_Handler();
    }
  }
  return Count;
}

signed TCPIP_Transmit(void *Buff, signed Size)
{
  signed Count = sizeof(com_port_transmit_data_buffer) - com_port_transmit_data_length;

  if (com_port_transmitting || !TCPIP_ComPort_Connected) return 0;
  if (Count > Size) Count = Size;
  if (Count == 0) return 0;
  memcpy(&com_port_transmit_data_buffer[com_port_transmit_data_length], Buff, Count);
  com_port_transmit_data_length += Count;
  return Count;
}

void TCPIP_Flush(void)
{
  if (com_port_transmitting || !TCPIP_ComPort_Connected) return;
  if (com_port_transmit_data_length == 0) return;
  com_port_transmitting = 1;
  if (tx_event_flags_set(&ComPortTransmitEventFlags, 0x00000001, TX_OR) != TX_SUCCESS)
  {
    Error_Handler();
  }
}

/******************************************************************************************************************************************************************************************************/
/**
* @brief  Modbus listen call back
* @param socket_ptr: NX_TCP_SOCKET socket registered for the callback
* @param port: UINT  the port on which the socket is listening
* @retval none
*/
static VOID modbus_listen_callback(NX_TCP_SOCKET *socket_ptr, UINT port)
{
  tx_semaphore_put(&ModbusSemaphore);
}

/**
* @brief  Modbus server thread entry
* @param thread_input: ULONG thread parameter
* @retval none
*/
static VOID App_Modbus_Thread_Entry(ULONG thread_input)
{
  UINT ret;
  NX_PACKET *data_packet;
  //  ULONG source_ip_address;
  //  UINT source_port;
  ULONG bytes_read;
  ULONG length;
  ULONG actual_flags;

  /* create the TCP socket */
  ret = nx_tcp_socket_create(&NetXDuoEthIpInstance, &ModbusSocket, "Modbus Server Socket", NX_IP_NORMAL, NX_FRAGMENT_OKAY,
                             NX_IP_TIME_TO_LIVE, WINDOW_SIZE, NX_NULL, NX_NULL);
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /*
  * listen to new client connections.
  * the Modbus_listen_callback will release the 'Semaphore' when a new connection is available
  */
  ret = nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, MODBUS_TCP_PORT, &ModbusSocket, MAX_TCP_CLIENTS, modbus_listen_callback);

  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
//    printf("Modbus Server listening on PORT %d ..\n", MODBUS_TCP_PORT);
//    OutputDebugNumber("MODBUS TCP listen: ", MODBUS_TCP_PORT);
  }

  if(tx_semaphore_get(&ModbusSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
    /* accept the new client connection before starting data exchange */
    ret = nx_tcp_server_socket_accept(&ModbusSocket, TX_WAIT_FOREVER);

    if (ret != NX_SUCCESS)
    {
      Error_Handler();
    }
//    OutputDebugNumber("MODBUS TCP accept: ", MODBUS_TCP_PORT);
    TCPIP_ModbusTCP_Connected = 1;
  }

  while(1)
  {
    ULONG socket_state;

    TX_MEMSET(modbus_receive_data_buffer, '\0', sizeof(modbus_receive_data_buffer));

    /* get the socket state */
    nx_tcp_socket_info_get(&ModbusSocket, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &socket_state, NULL, NULL, NULL);

    /* if the connections is not established then accept new ones, otherwise start receiving data */
    if(socket_state != NX_TCP_ESTABLISHED)
    {
      TCPIP_ModbusTCP_Connected = 0;
      ret = nx_tcp_server_socket_accept(&ModbusSocket, NX_IP_PERIODIC_RATE);
//      if (ret == NX_SUCCESS) OutputDebugNumber("MODBUS TCP accept: ", MODBUS_TCP_PORT);
      TCPIP_ModbusTCP_Connected = 1;
    }

    if(ret == NX_SUCCESS)
    {
      /* receive the TCP packet send by the client */
      ret = nx_tcp_socket_receive(&ModbusSocket, &data_packet, NX_WAIT_FOREVER);

      if (ret == NX_SUCCESS)
      {
//        HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);
//        OutputDebug("MODBUS TCP receive");

        /* get the client IP address and  port */
//        nx_udp_source_extract(data_packet, &source_ip_address, &source_port);

        /* print the received data */
//        PRINT_DATA(source_ip_address, source_port, modbus_receive_data_buffer);

        nx_packet_length_get(data_packet, &length);

        if (length <= sizeof(modbus_receive_data_buffer))
        {
          /* retrieve the data sent by the client */
          nx_packet_data_retrieve(data_packet, modbus_receive_data_buffer, &bytes_read);
          modbus_receive_data_offset = 0;
          modbus_receive_data_length = bytes_read;
          ModbusTCP_ReceiveReady();
          if (tx_event_flags_get(&ModbusReceiveEventFlags, 0x00000001, TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER) != TX_SUCCESS)
          {
            Error_Handler();
          }
        }

        nx_packet_release(data_packet);
      }
      else
      {
        nx_tcp_socket_disconnect(&ModbusSocket, NX_WAIT_FOREVER);
        nx_tcp_server_socket_unaccept(&ModbusSocket);
        nx_tcp_server_socket_relisten(&NetXDuoEthIpInstance, MODBUS_TCP_PORT, &ModbusSocket);
      }
    }
    else
    {
      /*toggle the green led to indicate the idle state */
//      HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);
      tx_thread_sleep(10);
    }
  }
}

static VOID App_Modbus_Transmit_Thread_Entry(ULONG thread_input)
{
  while(1)
  {
    UINT ret;
    NX_PACKET *data_packet;

    ULONG actual_flags;

    if (tx_event_flags_get(&ModbusTransmitEventFlags, 0x00000001, TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER) != TX_SUCCESS)
    {
      Error_Handler();
    }

    if (modbus_transmit_data_length > 0)
    {
      nx_packet_allocate(&NxAppPool, &data_packet, NX_TCP_PACKET, NX_WAIT_FOREVER);
      nx_packet_data_append(data_packet, modbus_transmit_data_buffer, modbus_transmit_data_length, &NxAppPool, NX_WAIT_FOREVER);
      ret =  nx_tcp_socket_send(&ModbusSocket, data_packet, NX_WAIT_FOREVER);
      if (ret == NX_SUCCESS)
      {
      }
//      HAL_GPIO_TogglePin(HEARTBEAT_GPIO_Port, HEARTBEAT_Pin);
    }
    modbus_transmit_data_length = 0;
    modbus_transmitting = 0;
    ModbusTCP_TransmitReady();
  }
}

signed ModbusTCP_Receive(void *Buff, signed Size)
{
  signed Count = modbus_receive_data_length - modbus_receive_data_offset;

  if (Count > Size) Count = Size;
  if (Count == 0) return 0;
  memcpy(Buff, &modbus_receive_data_buffer[modbus_receive_data_offset], Count);
  modbus_receive_data_offset += Count;
  if (modbus_receive_data_offset == modbus_receive_data_length)
  {
    if (tx_event_flags_set(&ModbusReceiveEventFlags, 0x00000001, TX_OR) != TX_SUCCESS)
    {
      Error_Handler();
    }
  }
  return Count;
}

signed ModbusTCP_Transmit(void *Buff, signed Size)
{
  signed Count = sizeof(modbus_transmit_data_buffer) - modbus_transmit_data_length;

  if (modbus_transmitting || !TCPIP_ModbusTCP_Connected) return 0;
  if (Count > Size) Count = Size;
  if (Count == 0) return 0;
  memcpy(&modbus_transmit_data_buffer[modbus_transmit_data_length], Buff, Count);
  modbus_transmit_data_length += Count;
  return Count;
}

void ModbusTCP_Flush(void)
{
  if (modbus_transmitting || !TCPIP_ModbusTCP_Connected) return;
  if (modbus_transmit_data_length == 0) return;
  modbus_transmitting = 1;
  if (tx_event_flags_set(&ModbusTransmitEventFlags, 0x00000001, TX_OR) != TX_SUCCESS)
  {
    Error_Handler();
  }
}

/******************************************************************************************************************************************************************************************************/
/**
* @brief  Link thread entry
* @param thread_input: ULONG thread parameter
* @retval none
*/
static VOID App_Link_Thread_Entry(ULONG thread_input)
{
  ULONG actual_status;
  UINT linkdown = 1, status;

  while(1)
  {
    /* Send request to check if the Ethernet cable is connected. */
    status = nx_ip_interface_status_check(&NetXDuoEthIpInstance, 0, NX_IP_LINK_ENABLED,
                                      &actual_status, 10);

    if(status == NX_SUCCESS)
    {
      if(linkdown == 1)
      {
        linkdown = 0;

        /* The network cable is connected. */
//        printf("The network cable is connected.\n");
        TCPIP_Cable_Connected = 1;

        /* Send request to enable PHY Link. */
        nx_ip_driver_direct_command(&NetXDuoEthIpInstance, NX_LINK_ENABLE,
                                      &actual_status);

        /* Send request to check if an address is resolved. */
        status = nx_ip_interface_status_check(&NetXDuoEthIpInstance, 0, NX_IP_ADDRESS_RESOLVED,
                                      &actual_status, 10);
        if(status == NX_SUCCESS)
        {
          /* Stop DHCP */
          nx_dhcp_stop(&DHCPClient);

          /* Reinitialize DHCP */
          nx_dhcp_reinitialize(&DHCPClient);

          /* Start DHCP */
          nx_dhcp_start(&DHCPClient);

          /* wait until an IP address is ready */
          if(tx_semaphore_get(&DHCPSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS)
          {
            /* USER CODE BEGIN DHCPSemaphore get error */
            Error_Handler();
            /* USER CODE END DHCPSemaphore get error */
          }

//          PRINT_IP_ADDRESS(IpAddress);
        }
        else
        {
          /* Set the DHCP Client's remaining lease time to 0 seconds to trigger an immediate renewal request for a DHCP address. */
          nx_dhcp_client_update_time_remaining(&DHCPClient, 0);
        }
      }
    }
    else
    {
      if(0 == linkdown)
      {
        linkdown = 1;
        /* The network cable is not connected. */
//        printf("The network cable is not connected.\n");
        TCPIP_Cable_Connected = 0;

        nx_ip_driver_direct_command(&NetXDuoEthIpInstance, NX_LINK_DISABLE,
                                      &actual_status);
      }
    }

    tx_thread_sleep(NX_APP_CABLE_CONNECTION_CHECK_PERIOD);
  }
}
/* USER CODE END 1 */
