项目背景:
本人一直在寻找一种可以不知道具体设备信息的情况下,可以跨网快速发现设备,修改IP和远程控制的网络协议。比较了WS-Discovery,onvif,gvcp几种协议后,发现GigeVision的gvcp协议还是最简洁高效的,毕竟所有工业相机都在用,既然有这么好的协议,所以就全网搜索了一下,除了发现几篇比较好的介绍文章,竟然没有一个可以用的代码,所以就花了一周时间自己码了一个,不足之处请大家指正。
方案简介:
网口工业相机的基本方案是采用Gige-Vision的标准,采用gvcp协议来发现、配置和控制相机,采用gvsp协议来传输未压缩的图像数据,一般需要千兆网速。我的嵌入式设备运行ubuntu core系统,只有百兆网速,所以图像传输采用mjpg-streamer,可以支持uvc和mipi多种摄像头。我只要再实现gvcp的协议就可以实现工业相机的大部分功能,岂不乐哉。
硬件组成图
硬件采用100万全局曝光的OV9281作为图像采集芯片,主板采用H3的4核A7核心板,具有网口/WIFI/USB/GPIO等接口,大小40x40mm。 已测试通过的开源硬件链接为 https://item.taobao.com/item.htm?id=652142910541
64位升级板硬件已经发布,采用200万彩色摄像头作为图像采集芯片,主板采用4核A53处理器,内置Python和OpenCV,算力更强,支持深度学习推理,具有网口/WIFI/USB/GPIO等接口,大小40x40mm。 已测试通过的开源硬件链接为 https://item.taobao.com/item.htm?id=684106700771
软件架构示意图
本项目实现了一种基于GVCP协议和mjpeg-steamer的网口工业相机。使用GigeVision中的gvcp协议来实现对相机的发现,修改IP和远程控制功能,使用mjpeg-steamer来传输图像数据.配合PC端的OpenMVS软件使用,可以方便的查找相机、管理相机、获取图像,保存图片等功能。
GvcpServer的实现
在GvcpServer中,我主要实现了常用的几种协议
#define GVCP_DISCOVERY_CMD 2
#define GVCP_DISCOVERY_ACK 3
#define GVCP_FORCEIP_CMD 4
#define GVCP_FORCEIP_ACK 5
#define GVCP_READREG_CMD 0x80
#define GVCP_READREG_ACK 0x81
#define GVCP_WRITEREG_CMD 0x82
#define GVCP_WRITEREG_ACK 0x83
#define GVCP_READMEM_CMD 0x84
#define GVCP_READMEM_ACK 0x85
gvcp的发现命令代码如下
int gvcp_cmd_discover(int iFd)
{
//char rgMessage[128] = "I am sending message to you!";
//int iFd;
int iSendbytes;
struct sockaddr_in Addr;
int bNeedClose = 0;
if (iFd < 0)
{
if ((iFd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
printf("socket fail\n");
return -1;
}
bNeedClose = 1;
}
int iOptval = 1;
#ifdef _WINDOWS_
if (setsockopt(iFd, SOL_SOCKET, SO_REUSEADDR, (CHAR*)&iOptval, sizeof(int)) < 0)
{
printf("setsockopt SO_REUSEADDR failed!");
}
BOOL bBroadcast = true;
if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL)) < 0)
{
printf("setsockopt SO_BROADCAST failed!");
}
#else
if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, &iOptval, sizeof(int)) < 0)
{
printf("setsockopt failed!");
}
#endif
memset(&Addr, 0, sizeof(struct sockaddr_in));
Addr.sin_family = AF_INET;
Addr.sin_addr.s_addr = inet_addr("255.255.255.255");
Addr.sin_port = htons(3956);
static struct gvcp_discover_cmd cmd_msg;
memset(&cmd_msg, 0, sizeof(struct gvcp_discover_ack));
cmd_msg.header.cMsgKeyCode = 0x42;
cmd_msg.header.cFlag=0x11;//0x11 allow broadcast ack;ack required
cmd_msg.header.wCmd= htons(GVCP_DISCOVERY_CMD);//discovery_cmd=2;FORCEIP_CMD = 4;READREG_CMD=0x80
cmd_msg.header.wLen = htons(0);//payload length
cmd_msg.header.wReqID = htons(1);// request id = 1;READREG id=12345
char* rgMessage = (char*)&cmd_msg;
uint32 dwMsgLen = sizeof(struct gvcp_discover_cmd);
//while (1)
{
if ((iSendbytes = sendto(iFd, rgMessage, dwMsgLen, 0, (struct sockaddr*)&Addr, sizeof(struct sockaddr))) == -1)
{
printf("sendto fail, errno=%d,%s\n", errno, strerror(errno));
return -1;
}
printf("gvcp_cmd_discover=%s, rgMessageLen=%d,iSendbytes=%d\n", rgMessage, dwMsgLen, iSendbytes);
//sleep(1);
}
if (bNeedClose > 0)
{
#ifdef _WINDOWS_
closesocket(iFd);
#else
close(iFd);
#endif
}
return 0;
}
gvcp的发现应答命令如下:
int gvcp_ack_discover(int iFd,char* szIp,char* szMask,char* szGateway, uint16 wReqID,uint32 dwPort,uint8* pMac)
{
//char rgMessage[128] = "I am sending message to you!";
//int iFd;
int iSendbytes;
int iOptval = 1;
struct sockaddr_in Addr;
int bNeedClose=0;
if(iFd<0)
{
if ((iFd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
printf("socket fail\n");
return -1;
}
bNeedClose=1;
}
if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, (char*)&iOptval, sizeof(int)) < 0)
{
printf("setsockopt failed!");
}
memset(&Addr, 0, sizeof(struct sockaddr_in));
Addr.sin_family = AF_INET;
Addr.sin_addr.s_addr = inet_addr("255.255.255.255");
Addr.sin_port = htons(dwPort);
struct gvcp_discover_ack ack_msg;
memset(&ack_msg, 0, sizeof(struct gvcp_discover_ack));
ack_msg.header.wStatus=htons(0);
ack_msg.header.wAck=htons(GVCP_DISCOVERY_ACK);
ack_msg.header.wLen=htons(sizeof(struct gvcp_ack_payload));
ack_msg.header.wReqID=htons(1);
ack_msg.payload.dwSpecVer=htonl(0x010002);;
ack_msg.payload.dwDevMode=htonl(1);
//uint8 MyMac[6]={0xc4,0x2f,0x90,0xf1,0x71,0x3e};
memcpy(&ack_msg.payload.Mac[2],m_LocalMacAddr,6);
ack_msg.payload.dwSupIpSet=htonl(0x80000007);
ack_msg.payload.dwCurIpSet=htonl(0x00005);
//uint8 unused1[12];
*((uint32*)&ack_msg.payload.CurIP[12])=inet_addr(m_szLocalIp);//last 4 byte
*((uint32*)&ack_msg.payload.SubMask[12])=inet_addr(m_szLocalMask);//last 4 byte
*((uint32*)&ack_msg.payload.Gateway[12])=inet_addr(m_szLocalGateway);//last 4 byte
strcpy(ack_msg.payload.szFacName,"GEV");//first
strcpy(ack_msg.payload.szModelName,"MV-CA010-GM");//first
strcpy(ack_msg.payload.szDevVer,"V2.8.6 180210 143913");
strcpy(ack_msg.payload.szFacInfo,"GEV");
strcpy(ack_msg.payload.szSerial,"00C31976084");
strcpy(ack_msg.payload.szUserName,"");
char* rgMessage=(char*)&ack_msg;
uint32 dwMsgLen = sizeof(struct gvcp_discover_ack);
//while (1)
{
if ((iSendbytes = sendto(iFd, rgMessage, dwMsgLen, 0, (struct sockaddr*)&Addr, sizeof(struct sockaddr))) == -1)
{
printf("sendto fail, errno=%d,%s\n", errno,strerror(errno));
return -1;
}
printf("gvcp_ack_discover=%s, rgMessageLen=%d,iSendbytes=%d\n", rgMessage, dwMsgLen, iSendbytes);
//sleep(1);
}
if(bNeedClose>0)
{
#ifdef _WINDOWS_
closesocket(iFd);
#else
close(iFd);
#endif
}
return 0;
}
其他完整代码的安装步骤如下:
在ubuntu系统下,切换到su权限
git clone https://github.com/BigSensor/GvcpServer cd GvcpServer
make
./GvcpServer
mjpg-streamer代码的实现
mjpg-streamer的使用网上有很多介绍了,我不在赘述,有些板子里已经内置了mjpg-streamer的代码,直接运行就可,如果还没有的朋友,我付一个下载地址
git clone https://github.com/BigSensor/mjpg-streamer cd mjpg-streamer
make
./start
PC端管理软件OpenMVS的使用
本软件暂时叫OpenMVS,实现了一种工业相机的PC管理控制与播放软件,实现了GVCP的Client端和mjpeg-steamer的客户端。使用GigeVision中的gvcp协议来实现对相机的发现,修改IP和远程控制功能,使用http协议来读取并解码mjpeg-steamer传输图像数据。可以方便的查找相机、管理相机、获取图像,保存图片等功能。
目前只实现了windows端的功能,运行时只需要安装vc运行库即可。安装方式
git clone https://github.com/BigSensor/OpenMVS
软件已经实现了查询所有网卡下的gige设备,预览mjpg-streamer中的图片,保存图片等功能
后记:
本文的代码和程序已经上传到github中,大家可以下载、测试、批评、指正,有各种建议和意见请留言。