public:it:industrial_camera

工业相机

  • Industrial camera
  • 度申 do3think
  • 设备发现机制:GVCP Device Discovery
    • 使用UDP广播,不能跨路由,所以只限定同一局域网 a “limited broadcast” by RFC1122
    • Definitions of broadcast types are provided in section 3.3.6 of RFC1122
    • In GigE Vision 1.x, GVCP is only defined to listen on UDP port 3956.
    • GigE Vision version 2.0 defines how devices can advertise and discover each other using a combination of multicast DNS and DNS-SD, called Zeroconf Discovery
  • A GigE Vision device MUST have an XML device description file with the syntax described in the GenApi module of the GenICam standard
    • The filename of the XML device description file MUST be different when the file content changes.

GVCP

  • GVCP是一种基于UDP传输层协议的应用层协议,此控制协议不包括GigE视觉流媒体协议(GVSP)。
  • 控制设备的连接是唯一的,读取的连接可以多个
  • GVCP 的 IP头固定为20字节,不使用 IP 头可选项(linux socket 就是默认不使用)
  • The first GVCP port of the device MUST use UDP port 3956.This standard GVCP port has been registered with IANA (http://www.iana.org/assignments/port-numbers).
  • GVCP, 命令(command)和确认(ack)消息包含在同一个包中,这样能确保包不用碎片化分割,同理,GVCP的包大小不能超过 576 字节,因为可避免IP碎片化的最大传输单元(MTU)为576字节
  • 一个GVCP包的结构大小:
    Layer Size(in bytes)
    IP header (options not allowed) 20
    UDP header 8
    GVCP header 8
    Max. GVCP payload 540
    Total 576
  • GVCP 包必须32位对齐,header已满足条件,即要求有效载荷payload部分必须4字节对齐
  • 应用获取设备确认消息超时后,必须重发命令消息,重发命令消息时 req_id 保持不变
  • 重传消息默认是 200 ms 超时,次数3,这两个值都应可设置
  • req_id 初始值不能为0,由应用端按命令消息递增
  • 设备端的 ack_id 返回与命令消息一一对应,即重复的命令也会重复返回(但可能不会重复执行),应用端需处理收到多次同一 ack_id 消息的情况
  • 应用端必须忽略 ack_id 不匹配的消息,应用端必须收到前一条命令的ack确认消息后才能发送下一条命令(除了 DISCOVERY_CMD, PACKETRESEND_CMD)
  • 应用端要确保心跳连接。设备提供一个Heartbeat Timeout启动寄存器。建议应用端在该设备心跳超时时间内发送三次心跳消息,避免连接被设备端自动关闭
  • GVCP header 头结构: 第一字节值固定 0x42
  • 命令头结构 Command Messaeg Header gvcp_1.png

    COMMAND MESSAGE HEADER
    0x42 8 bits Hard-coded key code value used for message validation.
    flag 8 bits Upper 4 bits (msb, bit 0 to bit 3) are specific to each GVCP commands.
    ^^^^Lower 4 bits (lsb, bit 4 to bit 7) are common to all GVCP commands.
    ^^^^All unused bits must be set to 0.
    ^^^^bit 0 to 3 – Specific to each command. Set to 0 if the command does not use them.
    ^^^^bit 4 – Reserved, set to 0 on transmission, ignore on reception.
    ^^^^bit 5 – Reserved, set to 0 on transmission, ignore on reception.
    ^^^^bit 6 – Reserved, set to 0 on transmission, ignore on reception.
    ^^^^bit 7 – ACKNOWLEDGE:
    ^^^^ SET = 要求接收者必须发送 ack 确认消息.
    ^^^^ CLEAR = 接收者不应发送 ack 确认消息.
    ^^^^ ACKNOWLEDGE 的优先级比命令本身是否需要回复的优先级要高
    command 16 bits Command message value (see dictionary section).
    ^^^^Commands from 0 to 32765 are reserved for GigE Vision. Others are available for customization (device-specific).
    length 16 bits 负载 payload 的字节数,不包括头长度
    req_id 16 bits 递增的 req_id, 非0,应用端提供,设备端回复 ack_id 时拷贝相同值
  • ACK 消息头结构:

gvcp_2.png

ACKNOWLEDGE MESSAGE HEADER
status 16 bits Status of the requested operation (see status code section)
acknowledge 16 bits Acknowledge message value (see dictionary section).
length 16 bits 头之后的负载的字节数
ack_id 16 bits 值与所响应的命令消息的req_id 相同
  • GVCP必须使用网络字节顺序,即大端序 big-endian

DISCOVERY

  • DISCOVERY_CMD 值 0x0002
  • 命令消息头 flag 的 bit3 指示设备是否广播 DISCOVER_ACK 消息
  • 命令消息头 flag 的 bit7 必定设置为1,即设备端必须回复 ack 消息
  • DISCOVERY_ACK 值 0x0003 , 回复消息的头与负载结构(每行4字节,除了标明长度的):

    0 1 2 3
    status answer = DISCOVERY_ACK
    length ack_id
    spec_version_major spec_version_minor
    device_mode
    reserved device_MAC_address (high)
    device_MAC_address (low)
    IP_config_options
    IP_config_current
    reserved
    reserved
    reserved
    current_IP
    reserved
    reserved
    reserved
    current_subnet_mask
    reserved
    reserved
    reserved
    default_gateway
    manufacturer_name [32 bytes]
    model_name [32 bytes]
    device_version [32 bytes]
    manufacturer_specific_information [48 bytes]
    serial_number [16 bytes]
    user_defined_name [16 bytes]
  • 测试代码:

    #include <iostream>
    #include <cstring>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
     
    #include <iomanip>
     
    #define BROADCAST_PORT 3956 // GigE Vision discovery port
    #define BUFFER_SIZE 1024
     
    int main() {
        int sock;
        struct sockaddr_in broadcastAddr;
        char broadcastBuffer[BUFFER_SIZE];
        int broadcastPermission;
        unsigned int sendLen;
     
        // 创建UDP套接字
        sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (sock < 0) {
            std::cerr << "Socket creation failed." << std::endl;
            return -1;
        }
     
        // 设置广播权限
        broadcastPermission = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *) &broadcastPermission, sizeof(broadcastPermission)) < 0) {
            std::cerr << "Setting socket option to SO_BROADCAST failed." << std::endl;
            close(sock);
            return -1;
        }
     
        // 设置广播地址
        memset(&broadcastAddr, 0, sizeof(broadcastAddr));
        broadcastAddr.sin_family = AF_INET;
        broadcastAddr.sin_addr.s_addr = inet_addr("255.255.255.255"); // 广播地址
        broadcastAddr.sin_port = htons(BROADCAST_PORT);
     
        // 构造广播消息(这里简化了,实际应该按照GigE Vision协议构造)
        uint8_t discovery_msg[8] = {0};
        discovery_msg[0] = 0x42;
        discovery_msg[1] = 0x80;
        discovery_msg[2] = 0;
        discovery_msg[3] = 0x02; // DISCOVERY_CMD: 0x0002
        discovery_msg[4] = 0;
        discovery_msg[5] = 0;    // length 0
        discovery_msg[6] = 0;
        discovery_msg[7] = 1;    // req_id 1
     
        const size_t send_len = 8;
        // 发送广播消息
        if (sendto(sock, discovery_msg, send_len, 0, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) != send_len) {
            std::cerr << "Broadcast sendto() failed." << std::endl;
            close(sock);
            return -1;
        }
     
        std::cout << "Broadcast message sent. Waiting for responses..." << std::endl;
     
        // 接收响应(这里简化了,实际应该处理多个响应,并解析GigE Vision协议数据)
        struct sockaddr_in recvAddr;
        socklen_t addrLen = sizeof(recvAddr);
        int recvLen = recvfrom(sock, broadcastBuffer, BUFFER_SIZE, 0, (struct sockaddr *) &recvAddr, &addrLen);
     
        if (recvLen > 0) {
            broadcastBuffer[recvLen] = '\0';
            std::cout << "Received a response from: " << inet_ntoa(recvAddr.sin_addr) << std::endl;
            std::cout << "Size: " << recvLen << std::endl;
     
            std::cout << "Response: " << std::hex << std::setfill('0') << std::setw(2) << std::showbase;
     
            for (int i = 0; i < recvLen; ++i) {
                if (i >= 80 && broadcastBuffer[i] > 32 && broadcastBuffer[i] < 127) {
                    std::cout << broadcastBuffer[i] << " ";
                } else {
                    std::cout << (int)(broadcastBuffer[i]) << " ";
                }
     
                if (i > 0 && ((i+1) % 16 == 0)) {
                    std::cout << std::endl;
                }
            }
     
            std::cout << std::endl;
     
            char mac[7] = {0};
            memcpy(mac, broadcastBuffer + 18, 6);
     
            char ip[4] = {0};
            memcpy(ip, broadcastBuffer + 44, 4);
     
            char manufacturer_name[33] = {0};
            memcpy(manufacturer_name, broadcastBuffer + 80, 32);
            char model_name[33] = {0};
            memcpy(model_name, broadcastBuffer + 80 + 32, 32);
            char device_version[33] = {0};
            memcpy(device_version, broadcastBuffer + 80 + 64, 32);
            char manufacturer_spec_info[49] = {0};
            memcpy(manufacturer_spec_info, broadcastBuffer + 80 + 96, 48);
            char serial_number[17] = {0};
            memcpy(serial_number, broadcastBuffer + 80 + 96 + 48, 16);
            char user_defined_name[17] = {0};
            memcpy(user_defined_name, broadcastBuffer + 80 + 96 + 48 + 16, 16);
     
            std::cout << std::noshowbase << "mac: "
                << (int)(mac[0]) << ":"
                << (int)(mac[1]) << ":"
                << (int)(mac[2]) << ":"
                << (int)(mac[3]) << ":"
                << (int)(mac[4]) << ":"
                << (int)(mac[5]) << std::endl;
            std::cout << std::dec << std::setw(0) <<  "ip: "
                << (int)(ip[0]) << "."
                << (int)(ip[1]) << "."
                << (int)(ip[2]) << "."
                << (int)(ip[3]) << std::endl;
            std::cout << "manufacturer_name : " << manufacturer_name << std::endl;
            std::cout << "model_name : " << model_name << std::endl;
            std::cout << "device_version : " << device_version << std::endl;
            std::cout << "manufacturer_spec_info : " << manufacturer_spec_info << std::endl;
            std::cout << "serial_number : " << serial_number << std::endl;
            std::cout << "user_defined_name : " << user_defined_name << std::endl;
     
        } else {
            std::cerr << "No response received, or recvfrom() failed." << std::endl;
        }
        // 关闭套接字
        close(sock);
        return 0;
    }
     

FORCEIP

  • FORCEIP_CMD (0x0004)可以强制修改静态IP,
    • 前提1:知道相机的MAC地址
    • 前提2:相机当前没有控制应用端连接

channel 概念

  • 通道(channel)概念:A channel is a virtual link used to transfer information between GigE Vision entities. GigE Vision supports three types of channels:
    1. Control channel (there is always 1 control channel for the primary application)
    2. Stream channel (from 0 to 512 stream channels)
    3. Message channel (0 or 1 message channel)
  • 除了 3965 用于控制通道端口,其它通道可用任何端口
  • GVCP 定义了4级权限
    • Exclusive access: 独占权限,当应用程序具有独占访问权限时,设备不能允许第二个应用创建控制通道
    • Control access:控制权限,当应用程序具有控制权限时,设备可以允许第二个应用端具有读取权限
    • Control access with switchover enabled:可切换的控制权限,设备允许第二个具有正确凭据的应用端拥有控制权限
    • Monitor access: 监视权限,如果设备当前没有独占权限的应用端连接就可以使用
    • 一个设备只支持独占访问是完全合法的
    • 备忘:14.4节 讲述怎么让额外的应用端控制设备
    • 应用可在不关闭控制通道的情况下切换权限
  • PENDING_ACK:重置等待 ack 超时时间

gvcp_3.png

GVSP

  • A stream channel enables data transfer from a GVSP transmitter to a GVSP receiver. This transfer uses the GigE Vision Streaming Protocol (GVSP).
  • 允许 1-512 个连接
  • 也是基于 UDP IPv4, IP头也是固定20字节不使用IP头可选项
  • GVSP 的包大小不要求32位对齐,包大小取决于链路之间的MTU大小 -SCPSx 只决定数据负载(data payload)的包的大小, 数据头包(data leader)与尾包(data trailer)还是限定576字节大小的包。
  • 标准传输模式(Standard Transmission mode),把数据分为 data leader, data payload, data trailer 三部分package
  • 全入传输模式(All-in Transmission Mode),一个package全包含 data leader, payload, trailer. 可选,不一定支持
  • public/it/industrial_camera.txt
  • 最后更改: 2025/04/23 11:43
  • oakfire