www.pudn.com > mod_rssim6.zip > MODEthCommsProcessor.cpp
// CMODEthCommsProcessor.cpp: implementation of the Ethernet CMODEthCommsProcessor class.
//
// Ethernet:
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#define MAX_MOD_MESSAGELENGTH 2048
// --------------------------------- PackASCIIField ------------------------------
void PackASCIIField(CHAR **pBuffer, WORD value, WORD length)
{
CHAR tempStr[80];
CHAR formatStr[80];
// build a format string
sprintf(formatStr, "%%0%dX", length);
// plonk it in
sprintf(tempStr, formatStr, value);
strncpy(*pBuffer, tempStr, length);
// increment pointer
*pBuffer+=length;
**pBuffer = '\0'; // add a null
}
// ------------------------- GetBCC ---------------------------------
// PURPOSE: Calculates a messages BCC value and returns it
// NOTE:
// The message must be "[NNNNNNNN"
// The bcc may be "cccc" or come from a valid message from the PLC.
//
LONG GetBCC(CHAR * bccString, DWORD msgLen)
{
LONG accumulator = 0;
return (accumulator);
} // GetBCC
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
// constructor to create listen socket
CMODEthCommsProcessor::CMODEthCommsProcessor(int responseDelay,
BOOL MOSCADchecks,
BOOL modifyThenRespond,
BOOL disableWrites,
LONG PDUSize,
WORD portNum) : CDDKSrvSocket(portNum)
{
CString description;
InitializeCriticalSection(&stateCS);
description.Format("Starting comms emulation : %s", "MODBUS TCP/IP [host]");
SockDataMessage(description);
m_responseDelay = 0;
m_linger = FALSE;
m_responseDelay = responseDelay;
SetPDUSize(PDUSize);
SetEmulationParameters(MOSCADchecks, modifyThenRespond, disableWrites);
m_pWorkerThread->ResumeThread(); //start thread off here
}
// constructor to re-use the listen socket
CMODEthCommsProcessor::CMODEthCommsProcessor(int responseDelay,
BOOL MOSCADchecks,
BOOL modifyThenRespond,
BOOL disableWrites,
LONG PDUSize,
SOCKET * pServerSocket) : CDDKSrvSocket(0, 0, pServerSocket)
{
InitializeCriticalSection(&stateCS);
m_responseDelay = 0;
m_linger = FALSE;
m_responseDelay = responseDelay;
SetPDUSize(PDUSize);
SetEmulationParameters(MOSCADchecks, modifyThenRespond, disableWrites);
m_pWorkerThread->ResumeThread(); //start thread off here
}
CMODEthCommsProcessor::~CMODEthCommsProcessor()
{
}
// ------------------------------- SockDataMessage ------------------------------
void CMODEthCommsProcessor::SockDataMessage(LPCTSTR msg)
{
EnterCriticalSection(&stateCS);
OutputDebugString("##");
if (NULL!=pGlobalDialog)
pGlobalDialog->AddCommsDebugString(msg);
LeaveCriticalSection(&stateCS);
}
// ------------------------------ SockDataDebugger ------------------------------
void CMODEthCommsProcessor::SockDataDebugger(const CHAR * buffer, LONG length, BOOL transmit)
{
CString debuggerString;
char *data;
byte hiNib,loNib;
data = new char[(length*2)+1];
for (int i = 0; i < length; i++)
{
hiNib = ( *(buffer+i) >>4) & 0x0f;
loNib = ( *(buffer+i) ) & 0x0f;
data[(i*2)] = ( (hiNib < 0x0A) ? ('0' + hiNib) : ('A' + hiNib-10) );
data[(i*2)+1] = ( (loNib < 0x0A) ? ('0' + loNib) : ('A' + loNib-10) );
}
data[(length*2)] = '\0';
// call the base class
//CDDKSocket::SockDataDebugger(buffer, length, transmit);
if (transmit)
{
if (length)
pGlobalDialog->OnCharactersSent();
debuggerString.Format("TX:%s\n", data);
}
else
{
if (length)
pGlobalDialog->OnCharactersReceived();
debuggerString.Format("RX:%s\n", data);
}
OutputDebugString(debuggerString);
// Send to debugger list-box
if (length)
pGlobalDialog->AddCommsDebugString(debuggerString);
delete (data);
} // SockDataDebugger
// ------------------------------- SockStateChanged -----------------------
void CMODEthCommsProcessor::SockStateChanged(DWORD state)
{
EnterCriticalSection(&stateCS);
if (NULL != pGlobalDialog)
pGlobalDialog->m_ServerSocketState = state;
LeaveCriticalSection(&stateCS);
} // SockStateChanged
// ------------------------------- ActivateStation ---------------------------
void CMODEthCommsProcessor::ActivateStation(LONG stationID)
{
if (stationID>0 && stationIDm_microTicksCountDown[stationID] = pGlobalDialog->GetAnimationOnPeriod();
// it will count down untill it extinguishes
}
} // ActivateStation
// ------------------------------- StationIsEnabled ---------------------------
// Return TRUE if station is enabled
BOOL CMODEthCommsProcessor::StationIsEnabled(LONG stationID)
{
if (!pGlobalDialog)
return(FALSE);
if (stationID>0 && stationIDStationEnabled(stationID));
}
return TRUE;
} // StationIsEnabled
// --------------------------------- ProcessData -----------------------------
BOOL CMODEthCommsProcessor::ProcessData(SOCKET openSocket, CHAR *pBuffer, DWORD numBytes)
{
CHAR telegramBuffer[MAX_MOD_MESSAGELENGTH];
CHAR responseBuffer[MAX_MOD_MESSAGELENGTH];
CHAR debugStr[MAX_MOD_MESSAGELENGTH];
WORD responseLen;
BYTE *pDataPortion;
int i=0;
//WORD guardword1=1;
WORD requestMemArea; // telegram read/write are being referenced=0..MAX_MOD_MEMTYPES
//WORD guardword2=2;
WORD startRegister, endRegister, MBUSerrorCode=0;
BOOL MBUSError = TRUE;
WORD numBytesInReq;
WORD numRegs;
CString deb;
m_debuggerStep = 100;
// inc counter
pGlobalDialog->PacketsReceivedInc();
responseBuffer[0] = '\0';
// copy the Rx'd telegram neatly
memcpy(telegramBuffer, pBuffer, numBytes);
telegramBuffer[numBytes] = '\0';
// simulate the I/O and network delays
Sleep(m_responseDelay);
if (pGlobalDialog->m_Unloading)
return(TRUE); // stop processing during shutdown of the socket array to allow easy closing.
// parse the telegram
// 1. break up the telegram
CMODMessage::SetEthernetFrames();
CMODMessage modMsg(telegramBuffer, numBytes);
//check the station #
ActivateStation(modMsg.stationID);
if (!StationIsEnabled(modMsg.stationID))
{
CString msg;
// station off-line
msg.Format("Station ID %d off-line, no response sent\n", telegramBuffer[6]);
SockDataMessage(msg);
return(TRUE);
}
// 2. parse
//Get memory area which to update or retrieve from
requestMemArea = modMsg.GetAddressArea(modMsg.functionCode);//.type);
if (requestMemArea >= MAX_MOD_MEMTYPES)
{
// TO DO!
// handle the error
Beep(2000,200);
requestMemArea = 3; // for now just default to "Holding" for now!
}
//
// Validate that the request is a valid command code
//
startRegister = modMsg.address;
//endRegister = startRegister + modMsg.byteCount/2;
endRegister = startRegister + modMsg.byteCount;
if ((modMsg.functionCode == MOD_READ_COILS)|| // 01
(modMsg.functionCode == MOD_READ_DIGITALS)|| // 02
(modMsg.functionCode == MOD_READ_REGISTERS)|| // 04
(modMsg.functionCode == MOD_READ_HOLDING)|| // 03
(modMsg.functionCode == MOD_READ_EXTENDED)|| // 14
(modMsg.functionCode == MOD_WRITE_SINGLE_COIL)|| // 05
(modMsg.functionCode == MOD_WRITE_MULTIPLE_COILS)|| // 0F
(modMsg.functionCode == MOD_WRITE_HOLDING)|| // 10
(modMsg.functionCode == MOD_WRITE_SINGLEHOLDING)|| // 06 (testing)
(modMsg.functionCode == MOD_WRITE_EXTENDED) // 15
)
{
// Check the request length against our PDU size.
if ((modMsg.functionCode == MOD_READ_COILS)|| // 01
(modMsg.functionCode == MOD_READ_DIGITALS)|| // 02
(modMsg.functionCode == MOD_WRITE_MULTIPLE_COILS)) // 0F
numBytesInReq = modMsg.byteCount/8; // # bits
else
numBytesInReq = modMsg.byteCount*2; // # registers
if (numBytesInReq > m_PDUSize)
{
MBUSError = TRUE;
MBUSerrorCode = MOD_EXCEPTION_ILLEGALVALUE; // too long data field
}
else
MBUSError = FALSE;
}
else
{
MBUSError = TRUE;
MBUSerrorCode = MOD_EXCEPTION_ILLEGALFUNC; // 01
}
if (modMsg.m_packError)
{
// request message has a corrupted field somewhere
MBUSError = TRUE;
MBUSerrorCode = MOD_EXCEPTION_ILLEGALVALUE; // too long data field
}
// 3. build response
CMODMessage responseModMsg(modMsg); //Call copy constructor
//
// Do some more message validation tests etc.
//
if (!MBUSError)
{
if ((m_MOSCADchecks)&& // Is a (Analog/holding/extended register)
((requestMemArea == 2)||(requestMemArea == 3)||(requestMemArea == 4))
)
{
WORD startTable,endTable; // table #
WORD startCol,endCol; // col #
endTable = MOSCADTABLENUMBER(endRegister); // MOSCAD specify register # on the wire for the formula
endCol = MOSCADCOLNUMBER(endRegister);
startTable = MOSCADTABLENUMBER(startRegister);
startCol = MOSCADCOLNUMBER(startRegister);
// test that this request does not bridge 2 columns.
// , else we cannot job/request them together.
if ((endTable != startTable) ||
(endCol != startCol))
{
MBUSError = TRUE;
MBUSerrorCode = MOD_EXCEPTION_ILLEGALADDR; // 02
}
}
}
if (!MBUSError)
{
// if we want to disable all writes
if ((m_disableWrites) &&
((modMsg.functionCode == MOD_WRITE_SINGLE_COIL) ||
(modMsg.functionCode == MOD_WRITE_SINGLEHOLDING) ||
(modMsg.functionCode == MOD_WRITE_MULTIPLE_COILS) ||
(modMsg.functionCode == MOD_WRITE_HOLDING) ||
(modMsg.functionCode == MOD_WRITE_EXTENDED)
)
)
{
CString deb;
MBUSError = TRUE;
MBUSerrorCode = MOD_EXCEPTION_ILLEGALFUNC; // 02
deb.Format("Writting to registers or I/O is disabled!\n");
OutputDebugString(deb);
SockDataMessage(deb);
}
}
// do a address+length range check too
if (!MBUSError)
{
if (PLCMemory[requestMemArea].GetSize() < endRegister)
{
MBUSError = TRUE;
MBUSerrorCode = (PLCMemory[requestMemArea].GetSize() < startRegister ?
MOD_EXCEPTION_ILLEGALADDR:MOD_EXCEPTION_ILLEGALVALUE); // 02
}
}
if (MBUSError)
{
CString msg;
msg.Format("Modbus message in error x%02X\n", MBUSerrorCode);
OutputDebugString(msg);
SockDataMessage(msg);
}
//
// Request message seems error free, process it.
//
// 1st 3 bytes + any others up to data get
// added in at this time
responseModMsg.BuildMessagePreamble(MBUSError,
MBUSerrorCode);
//if a write is done then the copy constructor will have done all required for ack
// else a read must now pack into dataPtr onwards! and calc len etc!
// A read must now pack into dataPtr onwards! and calc len etc!
// writes must update our mem areas accordingly.
if (!MBUSError)
{
// 4. fill in the data portion of telegram
switch (modMsg.functionCode)
{
case MOD_READ_COILS : // 01 READ
case MOD_READ_DIGITALS : // 02 READ
pDataPortion = responseModMsg.dataPtr; //Get offset to fill in data
if (MAX_MOD_MEMWORDS >= modMsg.address + modMsg.byteCount)
{
WORD memValueTemp;
WORD bitOffset;
//#ifdef _COMMS_DEBUGGING
deb.Format("Read In/output from %d for %d bits.\n", modMsg.address, modMsg.byteCount);
OutputDebugString(deb);
SockDataMessage(deb);
//#endif
// pack SIM memory: one WORD of sim memory for every BIT in the data.
// Ie the sim uses one word for each I/O bit in modbus.
numBytesInReq = modMsg.byteCount/8;
if (modMsg.byteCount%8) // if we overflow the byte
numBytesInReq++;
for (i=0; i = modMsg.address + modMsg.byteCount)
{
WORD memValueTemp;
//#ifdef _COMMS_DEBUGGING
deb.Format("Read Register from %d for %d .\n", modMsg.address, modMsg.byteCount);
OutputDebugString(deb);
SockDataMessage(deb);
//#endif
for (i=0; i = modMsg.address + modMsg.byteCount)
{
// lock memory for writting
CMemWriteLock lk(PLCMemory.GetMutex());
pDataPortion = responseModMsg.dataPtr; //Get offset to fill in data
if (!lk.IsLocked())
//...Update
switch (modMsg.functionCode)
{
case MOD_WRITE_SINGLE_COIL :
{
CString deb;
deb.Format("Write single output %d.\n", modMsg.address);
OutputDebugString(deb);
SockDataMessage(deb);
}
//data gets copied in now
if (m_modifyThenRespond)
PLCMemory.SetAt(requestMemArea, modMsg.address, (*(WORD*)modMsg.dataPtr?1:0));
pDataPortion = responseModMsg.dataPtr; //Get offset to fill in data
*pDataPortion++ = (PLCMemory[requestMemArea][(modMsg.address)+i] ? 0xFF : 0x00);
*pDataPortion++ = 0x00;
if (!m_modifyThenRespond)
PLCMemory.SetAt(requestMemArea, modMsg.address, (*(WORD*)modMsg.dataPtr?1:0));
numRegs = 1; // repaint 1 item
break;
case MOD_WRITE_MULTIPLE_COILS :
// unpack into the SIM memory on WORD of sim memory for every BIT in the data
//WORD numBytes;
numBytesInReq = modMsg.count/8;
if (modMsg.count%8) // if we overflow a byte
numBytesInReq++;
{
CString deb;
deb.Format("Write multiple outputs from %d for %d bits.\n", modMsg.address, modMsg.count);
OutputDebugString(deb);
SockDataMessage(deb);
}
numRegs = numBytesInReq; // repaint X bits
for (i=0;iGetListDisplayedWidth();
pGlobalDialog->RedrawListItems(modMsg.GetAddressArea(modMsg.functionCode),
modMsg.address/(cols),
(modMsg.address+(numRegs-1))/(cols)
); // repaint only the needed rows
}
break;
} //end switch
}
else
{ // error occurred
pDataPortion = responseModMsg.dataPtr; // Get offset to fill in data
}
// THERE IS no CRC, so do not call BuildMessageEnd
responseModMsg.totalLen = (WORD)((LONG)pDataPortion-(LONG)responseModMsg.buffer);
// insert the frame info
//responseModMsg
memmove(&responseModMsg.buffer[ETH_PREAMBLE_LENGTH], responseModMsg.buffer, responseModMsg.totalLen);
responseLen = ETH_PREAMBLE_LENGTH + responseModMsg.totalLen; //SwapBytes(*(WORD*)(responseModMsg.preambleLenPtr)) + 6; //hdr;
memset(responseModMsg.buffer, 0 , ETH_PREAMBLE_LENGTH);
WORD tn = responseModMsg.GetEthernetTransNum();
*((WORD*)&responseModMsg.buffer[0]) = tn;
*((WORD*)&responseModMsg.buffer[4]) = SwapBytes(responseModMsg.totalLen);
// 6. send it back
m_debuggerStep = 102;
sprintf(debugStr, "Send %d bytes\n", responseLen);
OutputDebugString(debugStr);
//Send(openSocket, responseLen, responseBuffer, debugStr);
SockStateChanged(SOCKETCURRENTLY_WRITTING);
Send(openSocket, responseLen, responseModMsg.buffer, debugStr);
m_debuggerStep = 103;
// inc counter
pGlobalDialog->PacketsSentInc();
return (TRUE);
}
// ----------------------------- LoadRegisters -----------------------------
// load binary dump of the register values from file.
BOOL CMODEthCommsProcessor::LoadRegisters()
{
BOOL ret = CMOD232CommsProcessor::LoadRegistersIMP();
if (ret)
SockDataMessage("Register values loaded OK\n");
return (ret);
} // LoadRegisters
// ----------------------------- SaveRegisters -----------------------------
// Save binary dump of the register values from file.
BOOL CMODEthCommsProcessor::SaveRegisters()
{
BOOL ret = CMOD232CommsProcessor::SaveRegistersIMP();
if (ret)
SockDataMessage("Register values Saved OK\n");
return (ret);
} // SaveRegisters
// -------------------------- SetEmulationParameters ------------------------
void CMODEthCommsProcessor::SetEmulationParameters(BOOL moscadChecks,
BOOL modifyThenRespond,
BOOL disableWrites)
{
m_MOSCADchecks = moscadChecks;
m_modifyThenRespond = modifyThenRespond;
m_disableWrites = disableWrites;
}