精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
VISCA通信协议用于控制摄像机,索尼公司出品,一般通过RS232来通信,也可用RS485。摄像机控制常用于视频会议、远程教学类项目,是一个常用的课题。VISCA协议推出时间久,现在已经有新的通信协议,比如PELCO-P和PELCO-D,新协议比VISCA协议归整,更好用,但是VISCA协议支持的设备更全面。本文在数据包格式理解、C语言实现和C#实现方面进行了总结。
命令通信的基本单元称为包(Packet)。一个包的长度为3到16字节,由头部、消息体和结束符三部分组成。命令包的第一个字节称为命令头(Header)。高半字节由1 (最高位,固定为1)和发送方(控制者)地址(地址一般为0)组成,低半字节由0和设备(摄像机)地址(或称“编号”)组成,从组成格式看,可以外接的摄像机最多有7台,如向1号摄像机发送命令,则命令头为0x81。命令包最后一个字节为终结符号,固定为0xff。中间部分字节称为消息体。协议说明文档中将命令头写成“8x”,其中x表示摄像机地址。
命令共2类:普通命令(Command)及查询命令(Inquiry)。前者是直接发送命令到摄像机,后者是从摄像机获取到数据。
具体的命令包格式如下:
8X QQ RR … FF
其中QQ为命令分类,01表示普通命令,09表示查询命令。RR为类别码(Category code),也可理解为实体码,相当于摄像机的某个部分的编码。RR后面还有可能带实体的索引,表示实体的第几个,一般实体是只有1个。X表示摄像机地址。范围1~7。
每个命令均有响应包,格式如下:
X0 … … FF
其中X范围为9~F,数值为摄像机编号+8。以FF结束。发送普通命令时,摄像机会返回ACK响应,但查询命令不会返回ACK。
ACK响应包格式:X0 41 FF
普通命令响应包格式:X0 51 FF
查询命令响应包格式:X0 51 … FF
其中,X范围为9~F,是摄像机地址值+8。查询命令的响应包中带有数据,每种数据均不相同,可以询查协议文档。
错误信息格式如下:
语法错误:X0 61 02 FF
命令取消:X0 61 04 FF
没有socket:X0 61 05 FF
命令没有执行:X0 61 41 FF
其中X的值和上面的一样。“socket”的范围暂时还不太了解。这些值就是代码做出判断的依据。
对于协议文档中qprs这类的描述方式,直接将其放到16位的十六进制数据的各项(十六进制格式为0xAAAA)中即可。比如一个命令的响应包格式为“y0 50 0p 0q 0r 0s FF”,则实际得到的数据是0xpqrs。如“01 02 03 04”,对应数据为0x1234。反之亦然。在代码中用移位来实现即可。下面看几个经典的命令格式。
1、不带参数的命令
摄像机上电CAM_Power命令格式: 8x 01 04 00 02 FF
“8x”中的“x”表示摄像机编号。此类命令,直接按命令字段来组装即可。
2、带参数的命令
变焦CAM_Zoom命令格式为:8x 01 04 47 0p 0q 0r 0s FF。
“0p 0q 0r 0s”中的pqrs组成focus position参数。组装命令时,要将这个参数依次移位到对应的字段。假设参数值为0x1234,则对应的字段为“01 02 03 04”。
CAM_AFMode命令可以设置Active/Interval Time两个值,格式为:8x 01 04 27 0p 0q 0r 0s FF
“0p 0q”对应于movement time,“0r 0s”对应于Interval,组装命令时,要分别进行组装。方式见上。
3、查询类命令,不带参数
像CAM_PowerInq查询命令,发送8x 09 04 00 FF,直接返回y0 50 02 FF或y0 50 03 FF
其中“y0 50 02 FF”是返回的数据,y值为摄像机编号+8。对于此类命令,直接读取第3个字节即可得到对应的状态。
4、查询类命令,带参数
像CAM_ZoomPosInq命令,发送8x 09 04 47 FF,返回y0 50 0p 0q 0r 0s FF
在查询命令中,有大部分的命令是带有可变数据的,“y0 50 0p 0q 0r 0s FF”中的“0p 0q 0r 0s”需要移位后才能知道确切的值,对应的值为0xpqrs。
很庆幸,关于visca,已经有libvisca开源项目了(见参考资源)。下面参考这个项目,做了一些小修改,写一下关键的实现代码。
关于串口的打开、读写、关闭,在此不再多说。下面说一下命令的封装:
void _visca_append_byte(VISCAPacket_t *packet, unsigned char byte)
{
packet->bytes[packet->length]=byte;
(packet->length)++;
}
void _visca_init_packet(VISCAPacket_t *packet)
{
// set it to null
memset(packet->bytes, '\0', sizeof(packet->bytes));
// we start writing at byte 1, the first byte will be filled by the
// packet sending function(_visca_send_packet). This function will also append a terminator.
packet->length=1;
}
这两个函数是命令的填充,每次调用_visca_append_byte就填充一个字符,在未填充前,要调用_visca_init_packet来初始化包的长度。当然,实现上也可以直接用数组形式把每个命令合到一起发送出去。_visca_send_packet是填充头部和尾部数据,无须调用者进行考虑,调用者只需关注实际的命令数据即可。这也是使用了VISCAInterface_t的好处(后面写pelco实现的将会看到)。
int32_t _visca_send_packet(VISCAInterface_t *iface, VISCACamera_t *camera, VISCAPacket_t *packet)
{
// check data:
if ((iface->address>7)||(camera->address>7)||(iface->broadcast>1))
{
com_print("(%s): Invalid header parameters\n",__FILE__);
com_print("addr: %d %d broadcast: %d(0x%x)\n",iface->address,camera->address,
iface->broadcast,iface->broadcast);
return VISCA_FAILURE;
}
// build header:
packet->bytes[0]=0x80;
packet->bytes[0]|=(iface->address << 4);
if (iface->broadcast>0)
{
packet->bytes[0]|=(iface->broadcast << 3);
packet->bytes[0]&=0xF8;
}
else
{
packet->bytes[0]|=camera->address;
}
// append footer(0xff)
_visca_append_byte(packet,VISCA_TERMINATOR);
return _visca_write_packet_data(iface,packet);
}
命令响应包函数如下:
int32_t _visca_get_reply(VISCAInterface_t *iface, VISCACamera_t *camera)
{
// first message: -------------------
if (_visca_get_packet(iface)!= VISCA_SUCCESS)
return VISCA_FAILURE;
iface->type=iface->ibuf[1]&0xF0;
// skip ack messages
while (iface->type==VISCA_RESPONSE_ACK)
{
if (_visca_get_packet(iface)!=VISCA_SUCCESS)
return VISCA_FAILURE;
iface->type=iface->ibuf[1]&0xF0;
}
switch (iface->type)
{
case VISCA_RESPONSE_CLEAR:
return VISCA_SUCCESS;
break;
case VISCA_RESPONSE_ADDRESS:
return VISCA_SUCCESS;
break;
case VISCA_RESPONSE_COMPLETED:
return VISCA_SUCCESS;
break;
case VISCA_RESPONSE_ERROR:
return VISCA_CMDERROR;
break;
}
return VISCA_FAILURE;
}
里面一些宏定义如下:
/* response types */
#define VISCA_RESPONSE_CLEAR 0x40
#define VISCA_RESPONSE_ADDRESS 0x30
#define VISCA_RESPONSE_ACK 0x40
#define VISCA_RESPONSE_COMPLETED 0x50
#define VISCA_RESPONSE_ERROR 0x60
其实判断也十分简单,就是根据协议给出的错误码来一一判断。
其它的代码,直接参考libvisca即可,不在这里列出。
public struct VISCAMessage
{
public static byte Address;
public static byte Command1, Command2, Data1, Data2;
public static byte[] GetPanMoveMessage(uint address, byte command1, byte rr, byte rrsub, byte data1, byte data2, byte commsub1, byte commsub2)//云台控制方向类命令生成
{
if (address < 1 & address > 256)
throw new Exception("协议只支持256设备");
byte btemp = 8;
Address = (byte)(btemp << 4 | (byte)address);
return new byte[] { Address, command1, rr, rrsub, data1, data2, commsub1, commsub2, STX };
}
public static byte[] GetMessageNoData(uint address, byte command1, byte rr, byte rrsub, byte commsub1)//有命令附带,但没命令参数类生成
{
if (address < 1 & address > 256)
throw new Exception("协议只支持256设备");
byte btemp = 8;
Address = (byte)(btemp << 4 | (byte)address);
return new byte[] { Address, command1, rr, rrsub, commsub1, STX };
}
public static byte[] GetMessageNoDataSub(uint address, byte command1, byte rr, byte rrsub)//无命令附带,也无命令参数类生成
{
if (address < 1 & address > 256)
throw new Exception("协议只支持256设备");
byte btemp = 8;
Address = (byte)(btemp << 4 | (byte)address);
return new byte[] { Address, command1, rr, rrsub, STX };
}
}
1、开源的visca协议库:libvisca: http://damien.douxchamps.net/libvisca/
2、visca协议的wiki介绍:http://en.wikipedia.org/wiki/VISCA_Protocol