/*
MTDemo53: Communicate with a Modbus/RTU with COM 3
             (or Modbus/ASCII) slave device (PLC) 

Compiler: BC++ 3.1
Compile mode: large
Project: user.c
         v7000.c
         ..\LIB\8000L.Lib
         ..\LIB\TCPIPL.Lib
         ..\LIB\XS8_NNNN.Lib,   with NNNN being the lib's version.
         ..\Lib\MBT8_NNN.Lib,   Modbus/TCP library with NNN being the lib's version.
         ..\Lib\8017HL.Lib,     Modbus Library doesn't include 8017HL.Lib,
                                this project should include it.
         ..\Lib\8024L.Lib,		Modbus Library doesn't include 8024L.Lib,
                                this project should include it.
         ..\Lib\8080L.Lib,		Modbus Library doesn't include 8080L.Lib,
                                this project should include it.
         ..\Lib\8090L.Lib,		Modbus Library doesn't include 8090L.Lib,
                                this project should include it.                        

    Communication:
        7188/8000 <== COM port 3 ==> Modbus/RTU (or ASCII) slave device (PLC)
        
    Hardware: 7188/8000 + Modbus/RTU (or ASCII) slave device (PLC)
    
    Memory mapping 
        iMemory_DI[200]~[249]  <====>  PLC DI register [100]~[149]
        iMemory_DO[200]~[249]  <====>  PLC DO register [100]~[149]
        iMemory_AI[200]~[207]  <====>  PLC AI register [100]~[107]
        iMemory_AO[200]~[207]  <====>  PLC AO register [100]~[107]  


Refer 8000\843x883x\TCP\Doc\[Big5|Eng|Gb2312]\Vxcomm.htm
 8000\843x883x\TCP\Xserver\Xserver.htm
 8000\843x883x\TCP\Xserver\Function.htm
to get more information.

Note: to use MBT8_NNN.Lib, the project is a little different.
      You must remove vModbus.c from this project.

[11/Nov/2002] first release by Kevin

[27/May/2003] by Kevin
    add CheckModbusRequest to UserLoopFun

[31/May/2004] by Kevin
    update Xserver libriary to version 3.2.0 (XS8_3009.Lib ===> XS8_3200.Lib)
        1. add function PortUserStart to user.c
        2. add function Port9999Start to v7000.c
    
    update hardware library
        8000L.Lib ===> 8000E.Lib 
        8000.h    ===> 8000E.h 
        The new version library supports both 40M and 80M CPU.
        And it only supports the Ethernet version.
        Functions relative to COM2 are canceled.
        Thus the system can save about 2~3K bytes SRAM space.
        
[24/Dec/2004] by Kevin
    update Xserver libriary to version 3.2.1
    
    includes 8080L lib
    
    Add LED menu for network configuration                       
*/


#include <stdio.h>
#include <string.h>
#include <math.h>

#include "module1.h"
#include "MBTCP.h"          // Modbus/TCP Head file for I-8000E
#include "io_scan.h"

//#include "..\Lib\8017H.h"		// IOScan Library already include 8017H.h
//#include "..\Lib\8024.h"		// IOScan Library already include 8024.h 
//#include "..\Lib\8080.h"		// IOScan Library already include 8080.h
//#include "..\Lib\8090.h"      // IOScan Library already include 8090.h 

int ScanUserProcess(int iProcess);
int iScanUserProcess=0;

int iScanCOMPort=1;	// of Modbus Kernel

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

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())
}

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)
	    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.
		
	Please refer to demo9 & demo11 for example code
*/	

	//InitLib(); 	// InitModbus() already includes InitLib();.	
	
	//======= Begin Modbus Kernel =======
	int iRet,i;
	extern int _CpuSpeed;   //40=40M, others=80M

	for(i=0;i<800;i++)
    {
        iMemory_DO[i]=0;
        iMemory_AO[i]=0;
    }
    
	iRet=InitModbus(iMemory_DI,iMemory_DO,iMemory_AI,iMemory_AO);
	if(iRet!=0)
	{
		// Initial Modbus library error.
	}
	else
	{
		// Initial Modbus library ok.
	}
	
	Init_IO_Scan(iMemory_DI,iMemory_DO,iMemory_AI,iMemory_AO);
	//======= End Modbus Kernel =======
	if(iTotalCOMPort==4)
        if(_CpuSpeed==40)
            EnableMonitorCom4();
	//Configure the COM port that prints message
    SetBaudrate(1,115200);
    SetDataFormat(1,8,0,1);
	Set_COMEnableMode(1,_Programming);
	
	//Configure the COM port that links to the PLC.
    SetBaudrate(3,115200);
    SetDataFormat(3,8,0,1);
	Set_COMEnableMode(3,_Programming);							 
}

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 TCP port 10000, 
    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 iNet_Status=0,iOffset_Status=0;
void UserLoopFun(void)
{
	//======= Begin of Modbus Kernel =======
	UpdateIOModule();
    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==2)
	    iScanCOMPort=3;
	    
	if(iScanCOMPort>iTotalCOMPort)
		iScanCOMPort=1;
		
    if(iOffset_Status==0)
	    iNet_Status=SMMI_Net_CheckIn();
	    
	if(iNet_Status==0)    
	    iOffset_Status=SMMI_Offset_CheckIn();		
	//======= End of Modbus Kernel =======
	iScanUserProcess=ScanUserProcess(iScanUserProcess);
}


int UserCmd(unsigned char *Cmd,unsigned char *Response)
{
	int iRet;
	
	//======= Begin Modbus Kernel =======	
	if(Cmd[0]=='!')
	{
		iRet=Configuration(Cmd,Response);
		if(iRet==0)
			return 1;	/* return ok */
		else 
			return 0;	/* return ERROR */
	}
	//======= End Modbus =======
	
	else 
		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 */
}


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/7188XA/7188XB    */
/*      8000                   */
/*  version 1.3.0              */
/*  date: 29,Sep,2005          */
/*******************************/
int ScanUserProcess(int iProcess)
{
    /*
    iProcess: executes which process?
    return: executes which process next time?
    
    In MBDemo03, following code can map DO, DI, AO, AI registers 
    of the Modbus/RTU (or Modbus/ASCII) slave device (PLC) 
    to iMemory_DO, iMemory_DI,iMemory_AO, iMemory_AI continuously.
    If the register on the PLC is changed, 7188/8000 can get latest
    status after 4 scan processes.
    */
    
    int iPort,iRet,i;
    int iNextProcess;
    
    switch(iProcess)
    {
        case 0:
            // Add user's process here
            // For example, sends modbus request to modbus/slave device
            // to read DI
            iPort=3;
            // Read PLC DI[100]~[149]
            iRet=ModbusRTU_Master(iPort,1,2,200,100,50,500,1);
                // COM port, NetID, FC=2 to read DI, iMemory_DI base address, 
                // device memory base address, I/O count.
                // Timeout = 500 ms
            
            //Print the DI data (only first 10 data)
            if(iRet==NoError)
            {
		          for(i=0;i<10;i++)
                    printCom1("DI[%02d]=%d ",i,iMemory_DI[i]);
                printCom1("\n\r");    
            }
            else
                printCom1("Read DI error! Error code=%d\n\r",iRet);
                
            iNextProcess=1;
            break;
                    
        case 1:
            // Add user's process here
            // For example, sends modbus request to modbus/slave device
            // to write DO
            iPort=3;
            iMemory_DO[200]=0;
            iMemory_DO[201]=1;
            iMemory_DO[202]=0;
            iMemory_DO[203]=1;
            iMemory_DO[204]=0;
            
            // Write to PLC DO[100]~[149] 
            iRet=ModbusRTU_Master(iPort,1,15,200,100,50,500,1); 
                // COM port, NetID, FC=15 to write DO, iMemory_DO base address, 
                // device memory base address, I/O count.
                // Timeout = 500 ms
                
        	    if(iRet==NoError)
                        printCom1("Write DO ok.\n\r");
        	    else
                        printCom1("Write DO error! Error code=%d\n\r",iRet);
                
            iNextProcess=2;
            break;
            
        case 2:
            // Add user's process here
            // For example, sends modbus request to modbus/slave device
            // to read AI
            iPort=3;
            // Read PLC AI[100]~[107] 
            iRet=ModbusRTU_Master(iPort,1,4,200,100,8,500,1);
                // COM port, NetID, FC=4 to read AI, iMemory_AI base address, 
                // device memory base address, I/O count.
                // Timeout = 500 ms
        
            //Print the DI data
            if(iRet==NoError)
            {
		        for(i=0;i<8;i++)
                    printCom1("AI[%02d]=%04X ",i,iMemory_AI[i]);
                printCom1("\n\r");    
            }
            else
                printCom1("Read AI error! Error code=%d\n\r",iRet);
                
            iNextProcess=3;
            break;
           
        case 3:
            // Add user's process here
            // For example, sends modbus request to modbus/slave device
            // to read AO
            iPort=3;
            iMemory_AO[200]=0x0000;
            iMemory_AO[201]=0x1111;
            iMemory_AO[202]=0x2222;
            iMemory_AO[203]=0x3333;
            iMemory_AO[204]=0x4444;
            iMemory_AO[205]=0x5555;
            iMemory_AO[206]=0x6666;
            iMemory_AO[207]=0x7777;
            
            //Write PLC AO[100]~[107]
            iRet=ModbusRTU_Master(iPort,1,16,200,100,8,500,1); //  
                // COM port, NetID, FC=16 to write AO, iMemory_AO base address, 
                // device memory base address, I/O count.
                // Timeout = 500 ms
                
        	    if(iRet==NoError)
                        printCom1("Write AO ok.\n\r");
        	    else
                        printCom1("Write AO error! Error code=%d\n\r",iRet);     
                
            iNextProcess=0;
            break;          
            
        default:
            iNextProcess=0;
            break;                  
    }
    return iNextProcess;
}

void UserEnd(void)
{
	//Print("Call UserEnd().\r\n");
	StopUserTimerFun();
} 

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