每家PLC厂商都有自己的通讯协议,西门子S7协议,三菱有MC、倍福有ADS,然而没有统一性的接口协议。
金南瓜科技为适应每一家通讯,每一家设备商、MES和工厂等都需要针对每款产品开发相应的通讯接口。
OneConnectAPI为实现统一的接口,去适配每一家厂商的协议。为中国工控行业快速发展而贡献,每一家公司都需要重新制造轮子,这是非常浪费时间和金钱,同时也不能保证稳定性以及持续的维护。
我们采取高效的多线程处理方案,保证极其高效的读写性能,对电脑性能要求极其低,一直进行读写操作,CPU使用率不超过1%(Atom E3940下测试)。
用户可以在一台工控机上进行对上百台的PLC主动读写操作,我们在光伏行业大量应用和测试过。
我们在半导体行业深耕多年,积累大量的经验,实现功能的同时,也需要保证极其严格的稳定性,晶圆生成设备7*24小时不能出任何故障。
#pragma once
// 用于TCP/IP通讯
class CCommunication
{
public:
CCommunication();
virtual ~CCommunication();
// 释放
void Release();
void SetTimeout(int nMs); // 设置超时
void SetMode(bool bLongConnect); // 长连接或者短连接模式
void SetTcpIP(const char* pIP); // socket参数
void SetTcpPort(int nPort); // socket参数
int GetTimeout();
CResult Connect(); // TCP连接
void Disconnect(); // 断开通讯
CResult SendSyncData(CMyString pSendData, CMyString& pRecvData); // 数据发读
protected:
// 数据接收
void SetRecvComplete(int nSize); // 告诉通讯,数据接收完成
virtual void OnBeginRecv(); // 重写数据接收,开始接收数据,用于协议识别
virtual void OnDataRecv(char* pData, int nSize) = 0; // 重写数据接收,用于协议识别
virtual void OnCloseConnect(); // 通讯关闭
private:
void CloseConnect(); // TCP关闭连接
void InitSockWSA();
void ReleaseSockWSA();
// 接收数据线程
void CreateRecvThread(); // 创建接收线程
void ExitRecvThread(); // 退出接收线程
static void RunRecvThread(void* lp);
void RecvHandle();
bool IsExitThread();
void OneRecvData(); // 一次接收数据
void StartRecvData(); // 开始接收数据
void StopRecvData();
// 数据读取,从缓存内读取
CResult RecvData(CMyString& pRecvData);
private:
bool m_bLongConnect; // 长连接
int m_nRecvTimeout; // 接收超时
// socket
SOCKET m_hSock;
struct sockaddr_in m_pSA;
vCritical m_syncLock; // 每次只能单个tcp读写
vCritical m_syncLockTcp; // soekct操作
HANDLE m_hRecvTimeoutEvent; // 接收线程超时
HANDLE m_hRecvExitFinish; // 定时器线程退出完毕
HANDLE m_hRecvExit; // 接收退出
HANDLE m_hRecvStartData; // 开始接收数据
CMyString m_pRecvData; // 接收到的数据
long m_nRecvCode; // 读取错误代码
// 临时缓
int m_nSize;
int m_nRecvSize;
char m_pBuff[200];
};
#pragma once
#include "Communication.h"
class CFinsHandle : public CCommunication
{
public:
CFinsHandle();
CResult ReadMemoryData(int nAddr, int nSize, FINS_DATA_TYPE::ENUM nType, __int16* pData);
CResult WriteMemoryData(int nAddr, int nSize, FINS_DATA_TYPE::ENUM nType, __int16* pData);
private:
virtual void OnBeginRecv(); // 重写数据接收,开始接收数据
virtual void OnDataRecv(char* pData, int nSize); // 重写数据接收,用于协议识别
virtual void OnCloseConnect(); // 通讯关闭
// 检查答复数据是否错误
CResult CheckReplyDataIsError(char* pData, int nSize); // 检查答复数据是否错误
long GetFinsNodeAddress(); // 获取fins节点地址
CResult EstablishCommunicationByFins(); // 建立Fins通讯
private:
CMyString m_pRecvData;
bool m_bEstablishCommunicationByFins; // 与Fins建立通讯
int m_nIpNode; // IP节点
};
#pragma once
#include "Export/InterfaceExport.h"
#include "FinsHandle.h"
class CFinsReadWrite : public CInterfaceExport
{
public:
// 参数
virtual CResult SetParament(const char* pName, const char* pValue);
// virtual void SetEventNotify(); // 事件通知
// 初始化模块
virtual CResult Connect();
virtual CResult Disconnect();
// 释放
virtual void Release();
// 读出
virtual CResult Read(const char* pAddr, bool* pData, __int32 size, const char* pCtrlData);
virtual CResult Read(const char* pAddr, char* pData, __int32 size, const char* pCtrlData);
virtual CResult Read(const char* pAddr, __int16* pData, __int32 size, const char* pCtrlData);
virtual CResult Read(const char* pAddr, __int32* pData, __int32 size, const char* pCtrlData);
virtual CResult Read(const char* pAddr, __int64* pData, __int32 size, const char* pCtrlData);
// 写入
virtual CResult Write(const char* pAddr, bool* pData, __int32 size, const char* pCtrlData);
virtual CResult Write(const char* pAddr, char* pData, __int32 size, const char* pCtrlData);
virtual CResult Write(const char* pAddr, __int16* pData, __int32 size, const char* pCtrlData);
virtual CResult Write(const char* pAddr, __int32* pData, __int32 size, const char* pCtrlData);
virtual CResult Write(const char* pAddr, __int64* pData, __int32 size, const char* pCtrlData);
private:
CResult ReadData(int nAddr, __int16* pData, int nSize, int nDataType); // 读取2字节一次
CResult WriteData(int nAddr, __int16* pData, int nSize, int nDataType); // 读取2字节一次
void GetDataTypeAndAddr(string pAddr, int& nDataType, int& nAddr); // 获取数据地址
int GetDataArea(string pArea); // 获取数据区
private:
CFinsHandle m_pHandle;
};
#include "stdafx.h"
#include "Communication.h"
CCommunication::CCommunication()
{
m_nRecvTimeout = 5000;
m_bLongConnect = true;
m_hSock = INVALID_SOCKET;
InitSockWSA();
memset(&m_pSA, 0, sizeof(struct sockaddr_in));
m_hRecvExit = CreateEvent(NULL, TRUE, FALSE, NULL);
m_hRecvExitFinish = CreateEvent(NULL, TRUE, FALSE, NULL);
m_hRecvTimeoutEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
m_hRecvStartData = CreateEvent(NULL, TRUE, FALSE, NULL);
CreateRecvThread();
}
CCommunication::~CCommunication()
{
ExitRecvThread();
ReleaseSockWSA();
CloseHandle(m_hRecvExit);
CloseHandle(m_hRecvExitFinish);
CloseHandle(m_hRecvTimeoutEvent);
CloseHandle(m_hRecvStartData);
}
void CCommunication::InitSockWSA()
{
// 必须的
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
}
}
void CCommunication::ReleaseSockWSA()
{
// 必须的
if(WSACleanup() != 0)
{
}
}
// 释放
void CCommunication::Release()
{
}
// TCP连接
CResult CCommunication::Connect()
{
vLocker lock(&m_syncLockTcp);
if(m_hSock == INVALID_SOCKET)
{
m_hSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_hSock != INVALID_SOCKET)
{
if(connect(m_hSock, (struct sockaddr*)&m_pSA, sizeof(m_pSA)) < 0)
{
closesocket(m_hSock);
m_hSock = INVALID_SOCKET;
return CResult(FINS_SOCKET_CONNECT_FAIL, "FINS TCP连接失败,请查看网络是否有问题");
}
}
else
{
return CResult(FINS_SOCKET_CREATE_FAIL, "FINS SOCKET创建失败");
}
}
return CResult(0);
}
// TCP关闭连接
void CCommunication::CloseConnect()
{
vLocker lock(&m_syncLockTcp);
if (m_hSock != INVALID_SOCKET)
{
shutdown(m_hSock, 2);
closesocket(m_hSock);
m_hSock = INVALID_SOCKET;
OnCloseConnect();
}
}
// 设置超时
void CCommunication::SetTimeout(__int32 nMs)
{
if(nMs < 100)
{
nMs = 100;
}
m_nRecvTimeout = nMs;
}
int CCommunication::GetTimeout()
{
return m_nRecvTimeout;
}
// 长连接或者短连接模式
void CCommunication::SetMode(bool bLongConnect)
{
m_bLongConnect = bLongConnect;
}
// socket参数
void CCommunication::SetTcpIP(const char* pIP)
{
m_pSA.sin_family = AF_INET;
m_pSA.sin_addr.s_addr = inet_addr(pIP);
}
// socket参数
void CCommunication::SetTcpPort(__int32 nPort)
{
m_pSA.sin_family = AF_INET;
m_pSA.sin_port = htons(nPort);
}
// 数据发读
CResult CCommunication::SendSyncData(CMyString pSendData, CMyString& pRecvData)
{
vLocker lock(&m_syncLock);
// 连接
CResult rc = Connect();
if(rc.nCode != 0)
{
return rc;
}
// 清空原来的数据
m_pRecvData.SetSize(0);
// 发送数据
int res = send(m_hSock, pSendData.GetString(), pSendData.Size(), 0);
if(res == SOCKET_ERROR)
{
CloseConnect();
return CResult(FINS_SEND_FAIL, "FINS 发送数据失败,请查看网络是否正常");
}
// 数据读取
rc = RecvData(pRecvData);
// 短连接每次都关闭
if(!m_bLongConnect)
{
CloseConnect();
}
return rc;
}
// 数据读取
// 带超时处理
CResult CCommunication::RecvData(CMyString& pRecvData)
{
CResult rc;
StartRecvData();
// 等待接收
ResetEvent(m_hRecvTimeoutEvent);
DWORD rs = WaitForSingleObject(m_hRecvTimeoutEvent, m_nRecvTimeout);
// 成功返回值
if (rs == WAIT_OBJECT_0)
{
pRecvData = m_pRecvData;
}
// 超时等其他错误
else
{
StopRecvData();
CloseConnect();
rc.SetData(FINS_RECV_WAIT_TIMEOUT, "FINS 等待数据答复超时");
}
return rc;
}
// 创建接收线程
//
void CCommunication::CreateRecvThread()
{
ResetEvent(m_hRecvExitFinish);
_beginthread(RunRecvThread, 0, this);
}
// 退出接收线程
//
void CCommunication::ExitRecvThread()
{
CloseConnect();
SetEvent(m_hRecvExit);
SetEvent(m_hRecvStartData);
WaitForSingleObject(m_hRecvExitFinish, INFINITE);
}
// 定时器线程
void CCommunication::RunRecvThread(void* lp)
{
CCommunication* pMC = (CCommunication*)lp;
pMC->RecvHandle();
SetEvent(pMC->m_hRecvExitFinish); // 处理完毕
}
// 定时器
void CCommunication::RecvHandle()
{
while (!IsExitThread())
{
WaitForSingleObject(m_hRecvStartData, INFINITE);
if (IsExitThread())
{
break;
}
OneRecvData();
}
}
// 一次接收数据
void CCommunication::OneRecvData()
{
m_nSize = 200;
m_nRecvSize = recv(m_hSock, m_pBuff, m_nSize, 0);
if(m_nRecvSize == SOCKET_ERROR)
{
CloseConnect();
m_nRecvCode = FINS_SOCKET_CONNECT_ERR;
}
else
{
m_pRecvData.Append(m_pBuff, m_nRecvSize);
m_nRecvCode = 0;
OnDataRecv(m_pBuff, m_nRecvSize);
}
}
// 告诉通讯,数据接收完成
void CCommunication::SetRecvComplete(int nSize)
{
ResetEvent(m_hRecvStartData); // 复位接收
SetEvent(m_hRecvTimeoutEvent); // 接收成功
}
// 重写数据接收,开始接收数据,用于协议识别
void CCommunication::OnBeginRecv()
{
}
// 通讯关闭
void CCommunication::OnCloseConnect()
{
}
bool CCommunication::IsExitThread()
{
if(WaitForSingleObject(m_hRecvExit, 0) == WAIT_OBJECT_0)
{
return true;
}
return false;
}
// 开始接收数据
void CCommunication::StartRecvData()
{
OnBeginRecv();
SetEvent(m_hRecvStartData);
}
// 停止接收数据
void CCommunication::StopRecvData()
{
ResetEvent(m_hRecvStartData);
}
// 断开通讯
void CCommunication::Disconnect()
{
CloseConnect();
}
#include "stdafx.h"
#include "FinsReadWrite.h"
// 参数
CResult CFinsReadWrite::SetParament(const char* pName, const char* pValue)
{
if(pName == nullptr ||
pValue == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
string pNameString = pName;
if (pNameString == API_PARAM_FINS_IP)
{
m_pHandle.SetTcpIP(pValue);
}
else if (pNameString == API_PARAM_FINS_PORT)
{
int nPort = ::atoi(pValue);
m_pHandle.SetTcpPort(nPort);
}
else if (pNameString == API_PARAM_FINS_TIMEOUT)
{
int nTimerOut = ::atoi(pValue);
m_pHandle.SetTimeout(nTimerOut);
}
return CResult(0);
}
// 初始化模块
CResult CFinsReadWrite::Connect()
{
return m_pHandle.Connect();
}
CResult CFinsReadWrite::Disconnect()
{
m_pHandle.Disconnect();
return CResult(0);
}
// 释放
void CFinsReadWrite::Release()
{
delete this;
}
// bool
CResult CFinsReadWrite::Read(const char* pAddr, bool* pData, __int32 nSize, const char* pCtrlData)
{
return CResult(0);
// long nCode = 0;
// int nDataType; // 数据类型
// int nAddr; // 数据地址
//
// GetDataTypeAndAddr(pAddr, nDataType, nAddr);
// return ReadData(nAddr, (__int16*)pData, nSize * 2, nDataType);
}
// 1字节
CResult CFinsReadWrite::Read(const char* pAddr, char* pData, __int32 nSize, const char* pCtrlData)
{
if (pAddr == nullptr ||
pData == nullptr ||
pCtrlData == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
int nDataType; // 数据类型
int nAddr; // 数据地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
CResult rc;
if(nSize % 2)
{
// 奇数处理
int nReadSize = nSize;
nReadSize++;
__int16* pReadData = (__int16*)malloc(nReadSize);
if (pReadData == nullptr)
{
return FINS_MALLOC_FAIL;
}
// 读取数据
rc = ReadData(nAddr, pReadData, nReadSize / 2, nDataType);
if(rc.nCode == 0)
{
memcpy(pData, pReadData, nSize);
}
free(pReadData);
}
else
{
rc = ReadData(nAddr, (__int16*)pData, nSize / 2, nDataType);
}
return rc;
}
// 2字节
CResult CFinsReadWrite::Read(const char* pAddr, __int16* pData, __int32 nSize, const char* pCtrlData)
{
long nCode = 0;
int nDataType; // 数据类型
int nAddr; // 数据地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return ReadData(nAddr, pData, nSize, nDataType);
}
// 4字节
CResult CFinsReadWrite::Read(const char* pAddr, __int32* pData, __int32 nSize, const char* pCtrlData)
{
long nCode = 0;
int nDataType; // 数据类型
int nAddr; // 数据地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return ReadData(nAddr, (__int16*)pData, nSize * 2, nDataType);
}
// 8字节
CResult CFinsReadWrite::Read(const char* pAddr, __int64* pData, __int32 nSize, const char* pCtrlData)
{
long nCode = 0;
int nDataType; // 数据类型
int nAddr; // 数据地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return ReadData(nAddr, (__int16*)pData, nSize * 2, nDataType);
}
// 读取2字节一次
CResult CFinsReadWrite::ReadData(int nAddr, __int16* pData, int nSize, int nDataType)
{
FINS_DATA_TYPE::ENUM nType = (FINS_DATA_TYPE::ENUM)nDataType;
switch(nType)
{
case FINS_DATA_TYPE::DM:
case FINS_DATA_TYPE::W:
case FINS_DATA_TYPE::H:
{
return m_pHandle.ReadMemoryData(nAddr, nSize, nType, pData);
}
break;
}
// 没有对应地址区域
char pBuff[300] = {0};
sprintf(pBuff, "FINS 没有对应的数据地址区域:地址", nAddr);
return CResult(FINS_NOT_DATA_AREA, pBuff);
}
// bool
CResult CFinsReadWrite::Write(const char* pAddr, bool* pData, __int32 nSize, const char* pCtrlData)
{
return CResult(0);
// if (pAddr == nullptr ||
// pData == nullptr ||
// pCtrlData == nullptr)
// {
// return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
// }
//
// int nDataType; // 数据类型
// int nAddr; // 数据地址
//
// // 获取PLC区域和地址
// GetDataTypeAndAddr(pAddr, nDataType, nAddr);
// return WriteData(nAddr, pData, nSize, nDataType);
}
// 1字节
CResult CFinsReadWrite::Write(const char* pAddr, char* pData, __int32 nSize, const char* pCtrlData)
{
if (pAddr == nullptr ||
pData == nullptr ||
pCtrlData == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
CResult rc;
int nDataType; // 数据类型
int nAddr; // 数据地址
// 获取PLC区域和地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
if(nSize % 2)
{
// 奇数处理
int nReadSize = nSize;
nReadSize++;
__int8* pReadData = (__int8*)malloc(nReadSize);
if (pReadData == nullptr)
{
return FINS_MALLOC_FAIL;
}
memset(pReadData, 0, nReadSize);
memcpy(pReadData, pData, nSize);
// 读取数据
rc = WriteData(nAddr, (__int16*)pReadData, nReadSize / 2, nDataType);
free(pReadData);
}
else
{
rc = WriteData(nAddr, (__int16*)pData, nSize / 2, nDataType);
}
return rc;
}
// 2字节
CResult CFinsReadWrite::Write(const char* pAddr, __int16* pData, __int32 nSize, const char* pCtrlData)
{
if (pAddr == nullptr ||
pData == nullptr ||
pCtrlData == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
int nDataType; // 数据类型
int nAddr; // 数据地址
// 获取PLC区域和地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return WriteData(nAddr, pData, nSize, nDataType);
}
// 4字节
CResult CFinsReadWrite::Write(const char* pAddr, __int32* pData, __int32 nSize, const char* pCtrlData)
{
if (pAddr == nullptr ||
pData == nullptr ||
pCtrlData == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
long nCode = 0;
int nDataType; // 数据类型
int nAddr; // 数据地址
// 获取PLC区域和地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return WriteData(nAddr, (__int16*)pData, nSize * 2, nDataType);
}
// 8字节
CResult CFinsReadWrite::Write(const char* pAddr, __int64* pData, __int32 nSize, const char* pCtrlData)
{
if (pAddr == nullptr ||
pData == nullptr ||
pCtrlData == nullptr)
{
return CResult(FINS_PARAM_POINT_ENTPY, "FINS 参数指针为空");
}
int nDataType; // 数据类型
int nAddr; // 数据地址
// 获取PLC区域和地址
GetDataTypeAndAddr(pAddr, nDataType, nAddr);
return WriteData(nAddr, (__int16*)pData, nSize * 4, nDataType);
}
// 读取2字节一次
CResult CFinsReadWrite::WriteData(int nAddr, __int16* pData, int nSize, int nDataType)
{
FINS_DATA_TYPE::ENUM nType = (FINS_DATA_TYPE::ENUM)nDataType;
switch(nType)
{
case FINS_DATA_TYPE::DM:
case FINS_DATA_TYPE::W:
case FINS_DATA_TYPE::H:
{
return m_pHandle.WriteMemoryData(nAddr, nSize, nType, pData);
}
break;
}
// 没有对应地址区域
char pBuff[300] = {0};
sprintf(pBuff, "FINS 没有对应的数据地址区域:地址", nAddr);
return CResult(FINS_NOT_DATA_AREA, pBuff);
}
// 获取数据地址
void CFinsReadWrite::GetDataTypeAndAddr(string pAddr, int& nDataType, int& nAddr)
{
unsigned int i = 0;
for (; i < pAddr.size(); i++)
{
if(pAddr[i] >= '0' && pAddr[i] <= '9')
{
break;
}
}
// 数据类型
string pType = pAddr.substr(0, i);
nDataType = GetDataArea(pType);
// 数据地址
string pStartAddr = pAddr.substr(i, pAddr.size() - i);
nAddr = ::atol(pStartAddr.c_str());
}
// 获取数据区
int CFinsReadWrite::GetDataArea(string pArea)
{
int nDataType = 0;
if (pArea == "DM") { nDataType = FINS_DATA_TYPE::DM; }
else if (pArea == "D") { nDataType = FINS_DATA_TYPE::DM; }
else if (pArea == "W") { nDataType = FINS_DATA_TYPE::W; }
else if (pArea == "H") { nDataType = FINS_DATA_TYPE::H; }
else { nDataType = FINS_DATA_TYPE::ERR; }
return nDataType;
}