/* MTDEMO00: Modbus/TCP to Modbus/RTU Master/Slave converter 

    Compiler: BC++ 3.1
    Compile mode: large
    Project: user.c
             v7000.c
             ..\LIB\7188EL.Lib
             ..\LIB\TCPIPL.Lib
             ..\LIB\VCOM_NNNN.Lib, with NNNN being the lib file's version.
             ..\LIB\MBT7_NNN.Lib,	Modbus/TCP library for 7188E, 
    								NNN being the lib's version.
    
    Communication:
        Modbus Master <= Modbus/TCP => Ethernet port of 7188EX 
        <==> [Modbus Kernel] <==> COM Ports of 7188EX <==> Modbus/RTU slave device
    
    The Modbus library has 4 main functions 
        1.  One Modbus/TCP slave port
        2.  Multi Modbus/RTU slave port 
            Each COM port can be configured as a Modbus/RTU slave port 
            or other functionality.
        3.  Modbus gateway function
        4.  Share memory
        3.  Programmable Modbus/RTU master function
        4.  
        1.  1 to N port Modbus/TCP to Modbus/RTU converter
            The Ethernet port is used to receive Modbus/Request from Modbus/TCP master.
            Other COM ports are used to link to Modbus/RTU slave devices.
            When gets Modbus/TCP request, the kernel can decide to send request to 
            which COM port and return the response to the Modbus/TCP master.
        2.	X board supports Modbus/TCP protocol
            Modbus/TCP master can send request to specific NetID(station number) to
            access the X board that mount on io expansion bus.
        3.	Supports user-defined function
            The internal registers are open for X board and user-defined process.
            You can use the internal register to store some special information.
        4.  Supports Modbus/RTU master function	
            Put the value to internal register, use function "ModbusMaster2Slave", 
            you can let 7188E be a Modbus/RTU master controller. 
        
    Hardware: 7188EX 
    
    Note: to use MBT7_NNN.Lib, the project is a little different to Xserver demos.
          You must remove vModbus.c from this project.

[25,Oct,2002] by Kevin   

[13,Mar,2005] by Kevin
    Update Xserver library from 3.0.9 to 3.2.1
        Needs to add two functions: PortUserStart(in user.c), 
                                    Port9999Start(in V7000.c). 

[2006,Feb,17] by Kevin
    Use new I/O memory mapping method.                                     
*/

#include <stdio.h>
#include "..\lib\7188e.h"
#include "..\lib\vxcomm.h"
#include "..\Lib\MBTCP.h"	// Modbus/TCP Head file for I-7188E


int iScanCOMPort=1;	// of Modbus Kernel

int iScanUserProcess=0;		

int ScanUserProcess(int iProcess);

//The I/O memory mapping         
unsigned char far iMemory_DI[100];
unsigned char far iMemory_DO[100];
          int far iMemory_AI[100];
          int far iMemory_AO[100];

void UserCount(void)
{
	// user's timer trigger function
	//
	// In this function cannot use any function that will use the hardware signal "clock",
	// Such as:
	//  1. ClockHigh(),ClockLow(), ClockHighLow(),
	//  2. Any EEPROM functions.
	//  3. Any 5DigitLed functions.
	//  4. Any NVRAM function.
	//  5. Any RTC function.(GetTime(),SetTime(),GetDate(),SetDate())
	//
	// refer to demo9 for example code
}

void UserInit(void)
{
/*
	In this function, user CAN:
	1. initialize user's program.
	2. set time interval for calling UserCount().
	3. set initial value of I/O or variables for UserLoopFun().
	4. set initial value of I/O or variables for another functions.
	5. change the default TCP PORT 10000/9999/502 to others value. [after vcom3004.lib]
	   Syntax:
    	Port10000=newport_10000;  for calling UserCmd       (user.c)
    	Port9999=newport_9999;    for calling VcomCmd7000   (v7000.c)
    	Port502=newport_502;      for calling VcomCmdModbus (vModbus.c) [after vcom3002.lib]
	    PortUser=newport_User;    for calling VcomCmdUser   (user.c)    [after vcom3005.lib]
	    
		Default port value:
		Port10000=10000;
		Port9999=9999;
		Port502=502;
		PortUser=0;
		If the port value is 0, Xserver will not 
		listen that port. That means the port will 
		be disable.
		
*/	
	int iRet;	
	
	//======= Begin of Modbus Kernel =======
	iRet=InitModbus(iMemory_DI,iMemory_DO,iMemory_AI,iMemory_AO);
	if(iRet==0)
	{
		// Initial Modbus configuration success.
	}
	else
	{
		// Initial Modbus configuration failure.
	}
	//======= End of Modbus Kernel=======

	// Put values to internal registers at initial.
	iMemory_AI[0]=0x1234;
	iMemory_AI[1]=0x4113;
	
	iMemory_AO[0]=0x1234;
	iMemory_AO[1]=0x5678;
	
	iMemory_DI[0]=12;		// all non-zero values make DI high
	iMemory_DI[1]=1;		// all non-zero values make DI high
	iMemory_DI[3]=0;		// only 0 make DI low
	
	iMemory_DO[0]=1;		// all non-zero values make DO hig
	iMemory_DO[1]=56;		// all non-zero values make DO high
	iMemory_DO[2]=0;		// only 0 make DO low
	
	Port9999=0;	//Disable listening TCP port 9999 to speed up 7188E.
}

void UserLoopFun(void)
{
	//======= Begin of Modbus Kernel =======
    if(mtModbusPort[iScanCOMPort].EnableMode==_ModbusRTU_Slave)
	    CheckModbusRTURequest(iScanCOMPort);		// Is any request from Modbus/RTU Master ?
	    
	if(mtModbusPort[iScanCOMPort].EnableMode==_ModbusASCII_Slave)
	    CheckModbusASCIIRequest(iScanCOMPort);		// Is any request from Modbus/ASCII Master ?
	    
	if(mtModbusPort[iScanCOMPort].EnableMode==_ModbusRTU_Gateway)
	{
		SendModbusRequest(iScanCOMPort);		// Passes request to modbus slave device.
		CheckResponseTimeout(iScanCOMPort);		// If response timeout, sets iModbusAction
												// to IDLE status.
		CheckModbusResponse(iScanCOMPort);		// Is any response from modbus slave device?
		SendModbusResponse(iScanCOMPort);		// Passes response to Modbus/RTU Master.
	}
	iScanCOMPort++;
	if(iScanCOMPort>iTotalCOMPort)
		iScanCOMPort=1;
	//======= End of Modbus Kernel =======
	
	iScanUserProcess=ScanUserProcess(iScanUserProcess);
	
}

int UserCmd(unsigned char *Cmd,unsigned char *Response)
{
	sprintf(Response,"%s",Cmd);
	return 1;		// return ok
}

int VcomUserBinaryCmd(TCPREADDATA *p)
{
 /* VXCOMM.EXE 2.6.12(09/04/2001) or later will support this function.

		TCP PORT 10000, command 23 will call this function.
		user can get the following message:
		p->ReadUartChar : the buffer store the command data(include "23")
		p->Length : the command data length(include the two byte "23")
		p->Socket : the socket number that receive the command, that is when the user function want
								return message to the client, just use the socket to send data.
								use:  VcomSendSocket(p->Socket,pdata,datalength);
 */
	VcomSendSocket(p->Socket,"User-defined command(23)",24);	// return 24 bytes.
	return 1;	/* any value will be accept */
}


void PortUserStart(int skt)
{
    /* 
    XS8_3200.Lib Version 3.2.00 (20,Apr,2004) 
    or later version supports this function.
    When a TCP/IP client connects to the 7188E/8000E via the user's defined port(PortUser), 
    the Xserver calls the function once.
    
    You can use function VcomSendSocket to send a message to the client 
    when a connection is established.
    
    For example:
        VcomSendSocket(skt,"Connection is established.",26); //return 26 bytes.
        
        skt: socket number assigned to the TCP/IP client.
    */
    
    skt=skt; //do nothing
}


int VcomCmdUser(TCPREADDATA *p)  
{ 
	/*
    VCOM3005 (Feb,22,2002) or later will call this function for PortUser.
    
    When packets received by TCP PORT PortUser(user defined) 
    of 7188E/8000E, Xserver will call this function.
    user can get the following message:
    p->ReadUartChar : the buffer store the command data.
                      Maximum length of p->ReadUartChar is 32767 bytes.
    p->Length : the command data length.
    p->Socket : the socket number that receive the command, that is when the user function wants 
                return message to the client, just use the socket to send data.
                usage:  VcomSendSocket(p->Socket,pdata,datalength);
	*/
  
	/* here just send back the command to the client. */
	VcomSendSocket(p->Socket,p->ReadUartChar,p->Length);
 	return 1;	/* any value will be accept */	
}


//===========================================================//
//  Functions about Modbus                                   //
//===========================================================//

/***************************/
/*  [ScanUserProcess]      */
/*  for 7188E/7188XB       */
/*  version 1.0.1          */
/*  date: 9,Aug,2002       */
/***************************/
int ScanUserProcess(int iProcess)
{
	// iProcess: executes which process?
	// return: executes which process next time?
	//
	// This function scans your special processes and stores the results 
	// to the memory. Thus, users can use Modbus protocol to 
	// read the memory to get the values of your special processes.
	// You should cut the hole scan process to many small processes.
	// That can reduce execution time of this function and 
	// speed up the scan rate.
	//
	// The total internal registers spaces is limited by 
	// constant MAX_REGISTER_COUNT in MBTCP_7E.h. You can change the number
	// to increase the total spaces.
	//
	// Xboard and user's process share the internal registers.
	// To use the internal registers, you must notice don't overlap
	// the registers.
	//
	// To program the COM port, you need to use the Modbus Utility to set
	// the COM port enable mode to programming mode.	
	
	int iPort,iRet,i;
	int iNextProcess;
	
	switch(iProcess)
	{
		case 0:
			// Add user's process here. 
			// You can put some result values (integer) to AI register array.
			// For example, sets AI[0x19] to 0x7188.
			iMemory_AI[0x19]=0x7188;
			iNextProcess=1;
			break;

		case 1:
			// Add user's process here.
			// You can put some result values (0 or 1) to DI register array.
			// For example, sets DI[0x75] to 1 (high).
			iMemory_DI[0x75]=1;		// Note: you must check if the register address
									//       over range of the MAX_REGISTER_COUNT
									//       constant that defined in MBTCP.h.
			iNextProcess=2;
			
		case 2:
			// Add user's process here.
			
			// For example, assume DI[0x04] is the alarm input.
			//     If AO[0x04]>=0x7188, turn on the alarm input.
			if(iMemory_AO[0x04]>=0x7188)
				iMemory_DI[0x04]=1;
			else 
				iMemory_DI[0x04]=0;
							
			iNextProcess=3;           			                 
			break;

		case 3:
			// Add user's process here.
			// you can read out DO register values to do something.
			// For example, if DO[0x01] is 1 (high), do something. 
			if(iMemory_DO[0x01]==1)
			{
				// Do something
			}			
			iNextProcess=0;           			                 
			break;
			
		//  You can expand the User's Process to 
		//  suit your requirments.
		
		default:
			iNextProcess=0;
			break;									
	}
	return iNextProcess;
}

void Modbus_Request_Event(char* CommandData,int* CommandLength)
{   
    /*
    Modbus_Request_Event is supported since version 1.6.8 [2007,03,13]. 
    
    char* CommandData: For Modbus/TCP, it includes 6 leading bytes. (needful) 
                       For Modbus/RTU, it includes 6 leading bytes. (needless) 
                       
    int* CommandLength: For Modbus/TCP, it includes 6 leading bytes. 
                        For Modbus/RTU, it includes 6 leading bytes.                      
    */
    /* Example code */
    //int i;    
    //printCom1("FC:%2d  StartAddress:%3d  IOCount:%4d\n\r",iModbusRequest_Fun, iModbusRequest_Addr,iModbusRequest_IOCount);
    //printCom1("Modbus Request\n\r    In==>");
	//for(i=0;i<*CommandLength;i++)
    //    printCom1("[%02X] ",CommandData[i]&0xFF);
}    

void Modbus_Response_Event(char* ResponseData,int* ResponseLength)
{
    /*
    char* ResponseData: For Modbus/TCP, it includes 6 leading bytes. 
                        For Modbus/RTU, it doesn't include 6 leading bytes
                       
    int* CommandLength: For Modbus/TCP, it includes 6 leading bytes. 
                        For Modbus/RTU, it doesn't include 6 leading bytes 
    */
    //If you want to change the content of the ResponseData, 
    //you have to do 2 steps for Modbus/TCP or Modbus/RTU.
    
    //Step1: Change content (Note:you must know the modbus protocol well)
    //ResponseData[6]=0x19;
    //ResponseData[7]=0x75;
    //ResponseData[8]=0x04;
    //ResponseData[9]=0x01;
    
    //Step2: Update data length
    //*ResponseLength=10;
    //int i;
    //printCom1("\n\r    Out==>");
	//for(i=0;i<*ResponseLength;i++)
    //    printCom1("[%02X] ",ResponseData[i]&0xFF);
    //printCom1("\n\r");  
}