linux 下 C++ 与三菱PLC 通过MC Qna3E 二进制 协议进行交互

news2025/1/11 10:53:15

西门子plc 有snap7库 进行交互,并且支持c++ 而且跨平台。但是三菱系列PLC并没有现成的开源项目,没办法只能自己拼接,我这里实现了MC 协议 Qna3E 帧,并使用二进制进行交互。

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>

#include <mutex>
#include <string>
using namespace std;

namespace MelsecMC
{
    class PlcSocket
    {
    private:
        bool is_open;
        int global_socket_fd; // 用于发送/接受数据
        mutex m;

    public:
        PlcSocket();
        ~PlcSocket();

        // 初始化socket
        bool initSocket(string ip, int port, int milSecond);
        // 关闭socket
        bool closeSocket();
        // 发送数据
        bool write(unsigned char *buffer, int len);
        // 接收数据
        bool read(unsigned char *buffer, int len);
    };
}
#include "socket.h"
#include <chrono>
#include <thread>

namespace MelsecMC
{
    PlcSocket::PlcSocket()
    {
        global_socket_fd = -1;
    }

    PlcSocket::~PlcSocket()
    {
    }

    bool PlcSocket::initSocket(string ip, int port, int milSecond)
    {
        // create
        int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_fd == -1)
        {
            cout << "socket 创建失败:" << endl;
            return false;
        }

        struct sockaddr_in addr;
        addr.sin_family = PF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr(ip.c_str());

        // connect
        int res = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr));
        if (res == -1)
        {
            cout << "connect 链接失败:" << endl;
            return false;
        }
        cout << "connect 链接成功:" << endl;

        // 设置timeout
        setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (char *)20, sizeof(int));
        setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)20, sizeof(int));

        cout << "setsockopt 成功:" << endl;
        global_socket_fd = socket_fd;

        return true;
    }
    bool PlcSocket::write(unsigned char *buffer, int len)
    {
        m.lock();
        long result = send(global_socket_fd, buffer, len, 0);
        m.unlock();
        if (result < 0)
        {
            return false;
        }
        return true;
    }
    bool PlcSocket::read(unsigned char *buffer, int len)
    {
        m.lock();
        long result = recv(global_socket_fd, buffer, len, 0);
        m.unlock();
        if (result < 0)
        {
            cout << "recv失败:" << endl;
            return false;
        }
        return true;
    }
    bool PlcSocket::closeSocket()
    {
        close(global_socket_fd);
        return true;
    }
}
/***
MC协议的通讯方式有很多种:4C、3C、2C、1C、4E、3E、1E帧格式 数据格式分为二进制格式和ASCII码格式
本代码采用 3E + 二进制格式
https://www.jianshu.com/p/ca7f1609c8c1
***/
#pragma once
#include <sys/sem.h>
#include <sys/shm.h>
#include <iostream>
#include <memory>
#include "socket.h"

namespace MelsecMC
{
    class MelsecMcClient : public std::enable_shared_from_this<MelsecMcClient>
    {
    public:
        using Ptr = std::shared_ptr<MelsecMcClient>;

        explicit MelsecMcClient();
        ~MelsecMcClient();
   
        bool connectTo(const string & ip, int port);
	    bool disconnect();

        bool readInt32(std::string area,int start,int &value);
        bool writeInt32(std::string area,int start,int value);

        bool readShort(std::string area,int start,short &value);
        bool writeShort(std::string area,int start,short value);

    private:
        PlcSocket socket;
        unsigned char head[7] = {0x50,0x00,0x00,0xFF,0xFF,0x03,0x00};
    
    private:
        unsigned char decodeArea(std::string area);
    };
}
#include "client.h"
#include <sstream>
#include <iomanip>
namespace MelsecMC
{
    MelsecMcClient::MelsecMcClient()
    {
    }

    MelsecMcClient::~MelsecMcClient()
    {
        disconnect();
    }

    bool MelsecMcClient::connectTo(const string &ip, int port)
    {
        return socket.initSocket(ip, port, 2000);
    }

    bool MelsecMcClient::disconnect()
    {
        return socket.closeSocket();
    }

    bool MelsecMcClient::writeInt32(std::string area,int start,int value)
    {
        unsigned char cmd[25] = {0};
        // 头
        memcpy(cmd, head, 7);

        //50 00 00 FF FF 03 00   10 00   0A 00   01 14  00 00    64 00 00   A8  02 00  01 00 00 00 写1
        //请求数据物理长度
        cmd[7] = 0x10;
        cmd[8] = 0x00;

        // CPU监视定时器 表示等待PLC响应的timeout时间
        cmd[9] = 0x0A;
        cmd[10] = 0x00;

        //写命令 跟读的差别是:读是0104,写是0114 ;就是04和14的差别
        cmd[11] = 0x01;
        cmd[12] = 0x14;

        //(子命令) : 值是0表示按字读写入1个字=16位),如果值是1就按位写入
        cmd[13] = 0x00;
        cmd[14] = 0x00;

        //(首地址):地址因为跨度比较大,所以用了3个字节;值640000  返过来是000064,十进制就是100
        cmd[17] = start / 255 / 255 % 255;
        cmd[16] = start / 255 % 255;
        cmd[15] = start % 255;

        //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
        unsigned char areaHex = decodeArea(area);
        if (areaHex == 0x00)
        {
            std::cout << "不存在的地址 " << area << std::endl;
            return false;
        }
        cmd[18] = areaHex;

        //写入长度 00 02  =10进制2个字 =32位 = 4个字节 =1个int
        cmd[19] = 0x02;
        cmd[20] = 0x00;

        //写入int值
        cmd[24] = (value >> 24) & 0xFF;
        cmd[23] = (value >> 16) & 0xFF;  
        cmd[22] = (value >> 8) & 0xFF;
        cmd[21] = value & 0xFF;

        if (!socket.write(cmd, sizeof(cmd)))
        {
            return false;
        }

        // 读取数据
        unsigned char recv[512] = {0};
        if (!socket.read(recv, sizeof(recv)))
        {
            return false;
        }
        if (recv[0] != 0xD0 && recv[1] != 0x00)
        {
            std::cout << "数据格式不正确" << std::endl;
            return false;
        }

        return true;
    }

    bool MelsecMcClient::readInt32(std::string area, int start, int &value)
    {
        unsigned char cmd[21] = {0};
        // 头
        memcpy(cmd, head, 7);

        //请求数据长度 也要反过来,值是000C,也就是12;表示后面的报文内容的长度是12
        cmd[7] = 0x0C;
        cmd[8] = 0x00;

        // CPU监视定时器 表示等待PLC响应的timeout时间
        cmd[9] = 0x0A;
        cmd[10] = 0x00;

        // 批量读命令 值是0401(所有值都要反过来看);表示批量读取;如果是1401就是随机写取;
        cmd[11] = 0x01;
        cmd[12] = 0x04;

        //(子命令) : 值是0表示按字读取(1个字=16位),如果值是1就按位读取
        cmd[13] = 0x00;
        cmd[14] = 0x00;

        //(首地址):地址因为跨度比较大,所以用了3个字节;值640000  返过来是000064,十进制就是100
        cmd[17] = start / 255 / 255 % 255;
        cmd[16] = start / 255 % 255;
        cmd[15] = start % 255;

        //(软元件 读取的区域) : 表示读取PLC寄存器的类型: 这里的A8表示D点;其他常见的有: 90-M点;9C-X点;9D-Y点;B0-ZR外部存储卡
        unsigned char areaHex = decodeArea(area);
        if (areaHex == 0x00)
        {
            std::cout << "不存在的地址 " << area << std::endl;
            return false;
        }
        cmd[18] = areaHex;

        // 读取长度 00 02  =10进制2个字 =32位 = 4个字节 =1个int
        cmd[19] = 0x02;
        cmd[20] = 0x00;

        // 发送数据
        if (!socket.write(cmd, sizeof(cmd)))
        {
            return false;
        }

        // 读取数据
        unsigned char recv[512] = {0};
        if (!socket.read(recv, sizeof(recv)))
        {
            return false;
        }

        // 解析数据
        // D0 00 00 FF FF 03 00 06 00 00 00 BB 02 96 49
        // D0 00 (响应) :表示反馈信息,固定D0 00
        // 00 (网络编号 ): 与上同
        // FF (PLC编号) : 与上同
        // FF 03 (请求目标模块IO编号) : 与上同
        // 00 (请求目标模块站编号): 与上同
        // 06 00  应答数据物理长度
        if (recv[0] != 0xD0 && recv[7] != 0x06)
        {
            std::cout << "数据格式不正确" << std::endl;
            return false;
        }
        value = recv[14] << 24 | recv[13] << 16 | recv[12] << 8 | recv[11];
        std::cout << "value " << value << std::endl;
        return true;
    }

    bool MelsecMcClient::readShort(std::string area,int start,short &value){
        unsigned char cmd[21] = {0};
        memcpy(cmd, head, 7);

        cmd[7] = 0x0C;
        cmd[8] = 0x00;

        cmd[9] = 0x0A;
        cmd[10] = 0x00;

        cmd[11] = 0x01;
        cmd[12] = 0x04;

        cmd[13] = 0x00;
        cmd[14] = 0x00;

        cmd[17] = start / 255 / 255 % 255;
        cmd[16] = start / 255 % 255;
        cmd[15] = start % 255;

        unsigned char areaHex = decodeArea(area);
        if (areaHex == 0x00)
        {
            std::cout << "不存在的地址 " << area << std::endl;
            return false;
        }
        cmd[18] = areaHex;

        cmd[19] = 0x01;
        cmd[20] = 0x00;

        if (!socket.write(cmd, sizeof(cmd)))
        {
            return false;
        }

        unsigned char recv[512] = {0};
        if (!socket.read(recv, sizeof(recv)))
        {
            return false;
        }

        if (recv[0] != 0xD0 && recv[7] != 0x04)
        {
            std::cout << "数据格式不正确" << std::endl;
            return false;
        }
        value = recv[12] << 8 | recv[11];
        std::cout << "value " << value << std::endl;
        return true;
    }
    
    bool MelsecMcClient::writeShort(std::string area,int start,short value){
        unsigned char cmd[23] = {0};
        memcpy(cmd, head, 7);

        cmd[7] = 0x0E;
        cmd[8] = 0x00;

        cmd[9] = 0x0A;
        cmd[10] = 0x00;

        cmd[11] = 0x01;
        cmd[12] = 0x14;

        cmd[13] = 0x00;
        cmd[14] = 0x00;

        cmd[17] = start / 255 / 255 % 255;
        cmd[16] = start / 255 % 255;
        cmd[15] = start % 255;

        unsigned char areaHex = decodeArea(area);
        if (areaHex == 0x00)
        {
            std::cout << "不存在的地址 " << area << std::endl;
            return false;
        }
        cmd[18] = areaHex;

        cmd[19] = 0x01;
        cmd[20] = 0x00;

        //写入short值 
        cmd[22] = (value >> 8) & 0xFF;
        cmd[21] = value & 0xFF;

        if (!socket.write(cmd, sizeof(cmd)))
        {
            return false;
        }

        unsigned char recv[512] = {0};
        if (!socket.read(recv, sizeof(recv)))
        {
            return false;
        }
        if (recv[0] != 0xD0 && recv[1] != 0x00)
        {
            std::cout << "数据格式不正确" << std::endl;
            return false;
        }

        return true;
    }

    unsigned char MelsecMcClient::decodeArea(std::string area)
    {
        if (area == "D")
        {
            return 0xA8;
        }
        else if (area == "M")
        {
            return 0x90;
        }
        else if (area == "X")
        {
            return 0x9C;
        }
        else if (area == "Y")
        {
            return 0x9D;
        }
        else if (area == "ZR")
        {
            return 0xB0;
        }
        else
        {
            return 0x00;
        }
    }
}
#include "client.h"

using namespace MelsecMC;

int main(int argc, char **argv)
{
    MelsecMcClient::Ptr client = std::make_shared<MelsecMcClient>();
    client->connectTo("10.10.14.60",6000);

    //int value;
    //melsecMcNet->readInt32("D",100,value);

    //client->writeInt32("D",101,122234);
    //client->writeShort("D",102,223);
    short value;
    client->readShort("D",102,value);
    return 0;
}

可利用 这个工具进行测试:

 协议参考:

https://www.jianshu.com/p/ca7f1609c8c1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/981656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux c++ 开发 - 05- 使用CMake创建一个动态库

外层CMakeList.txt中的内容&#xff1a; cmake_minimum_required(VERSION 3.16) PROJECT(HELLO) ADD_SUBDIRECTORY(lib bin)lib中CMakeLists.txt中的内容&#xff1a; SET(LIBHELLO_SRC hello.cpp) ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})hello.h: hello.cpp: ADD_LIBR…

Liquid Studio 2023.2 Crack

Liquid Studio 提供了用于XML和JSON开发 的高级工具包以及Web 服务测试、数据映射和数据转换工具。 开发环境包含一整套用于设计 XML 和 JSON 数据结构和模式的工具。这些工具提供编辑、验证和高级转换功能。对于新手或专家来说&#xff0c;直观的界面和全面的功能将帮助您节省…

C++回顾录

代码随想录 (programmercarl.com) 数组和内存 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 举一个字符数组的例子&#xff0c;如图所示&#xff1a; 数组可以方便的通过下标索引的方式获取到下标下对应的…

使用docker搭建owncloud Harbor 构建镜像

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 2、安装搭建私有仓库 Harbor 3、编写Dockerfile制作Web应用系统nginx镜像&#xff0c;生成镜像nginx:v1.1&#xff0c;并推送其到私有仓库。具体要求如下&#xff1a; &#xff08;1&#xff09;基于centos基础…

Scala面向对象编程(高级部分)

1. 静态属性和静态方法 &#xff08;1&#xff09;回顾Java中的静态概念 public static 返回值类型 方法名(参数列表) {方法体} 静态属性… 说明: Java中静态方法并不是通过对象调用的&#xff0c;而是通过类对象调用的&#xff0c;所以静态操作并不是面向对象的。 &#xff0…

SpringBoot 统一异常处理

1. 统一返回结果集 package com.liming.pojo;import com.liming.exception.AppExceptionCodeMsg; import lombok.Data;/*** 全局统一返回结果类* author 黎明* date 2023/9/6 14:11* version 1.0*/ Data public class Result<T> {private Integer code; // 状态码privat…

C#模拟PLC设备运行

涉及&#xff1a;控件数据绑定&#xff0c;动画效果 using System; using System.Windows.Forms;namespace PLCUI {public partial class MainForm : Form{ public MainForm(){InitializeComponent();}private void MainForm_Load(object sender, EventArgs e){// 方式2&#x…

基于SpringBoot的校园失物招领系统

基于SpringBooVuet的校园失物招领系统&#xff0c;前后端分离 附万字文档 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 角色&#xff1a;管理员、用户 管理员  …

线性空间、子空间、基、基坐标、过渡矩阵

线性空间的定义 满足加法和数乘封闭。也就是该空间的所有向量都满足乘一个常数后或者和其它向量相加后仍然在这个空间里。进一步可以理解为该空间中的所有向量满足加法和数乘的组合封闭。即若 V 是一个线性空间&#xff0c;则首先需满足&#xff1a; 注&#xff1a;线性空间里面…

AR工业远程巡查系统:实时监控设备状态,及时发现潜在问题

随着工业4.0的到来&#xff0c;先进的技术和创新的解决方案正在改变着工业生产的方式。其中&#xff0c;增强现实&#xff08;AR&#xff09;技术带来的工业巡检系统就是一个典型的例子。这种系统通过在现实世界中添加虚拟信息&#xff0c;使得操作人员能够更有效地进行检查和维…

leetcode 1002. 查找共用字符

2023.9.6 个人感觉这题难度不止简单&#xff0c;考察到的东西还是挺多的。 首先理解题意&#xff0c;可以将题意转化为&#xff1a;求字符串数组中 各字符串共同出现的字符的最小值。 分为三步做&#xff1a; 构造一个哈希表hash&#xff0c;初始化第一个字符串的字母出现频率…

2020年12月 C/C++(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 第1题&#xff1a;字符三角形 描述 给定一个字符&#xff0c;用它构造一个底边长5个字符&#xff0c;高3个字符的等腰字符三角形。 输入 输入只有一行&#xff0c; 包含一个字符。 输出 该字符构成的等腰三角形&#xff…

Zookeeper简述

数新网络-让每个人享受数据的价值 官网现已全新升级—欢迎访问&#xff01; 前 言 ZooKeeper是一个开源的、高可用的、分布式的协调服务&#xff0c;由Apache软件基金会维护。它旨在帮助管理和协调分布式系统和应用程序&#xff0c;提供了一个可靠的平台&#xff0c;用于处理…

苹果与芯片巨头Arm达成20年新合作协议,将继续采用芯片技术

9月6日消息&#xff0c;据外媒报道&#xff0c;芯片设计巨头Arm宣布在当地时间周二提交给美国证券交易委员会&#xff08;SEC&#xff09;的最新IPO文件中&#xff0c;透露与苹果达成了一项长达20年的新合作协议&#xff0c;加深了双方之间的合作关系。 报道称&#xff0c;虽然…

Informatica使用操作流程及Expression(表达式转换)案例2

操作流程 ①定义源<Odbc01_oracle:employees> ②定义目标<EDW_EMPLOYEES> ③创建映射<M_ORACLE_EDW01_employees> ④定义任务<S_ORCL_EDW01_employees> ⑤创建工作流<W_ORCL_EDW01_employees> ⑥工作流调度监控 ⑦查验数据 一、需求&…

js---16-----JavaScript中的类型转换机制

、类型转换机制是什么&#xff1f; JS中有六种简单数据类型&#xff1a;undefined、null、bollean、string、number、symbol&#xff0c;以及引用类型object 但是我们声明的时候只有一种数据类型&#xff0c;只用运行期间才会确定当前类型。 上面代码中&#xff0c;x的值在编…

浅谈redis未授权漏洞

redis未授权漏洞 利用条件 版本比较高的redis需要修改redis的配置文件&#xff0c;将bind前面#注释符去掉&#xff0c;将protected-mode 后面改为no 写入webshell config get dir #查看redis数据库路径 config set dir web路径# #修改靶机Redis数据库路径 config set dbfilen…

巧用抽象类与接口,打造高效Java程序(上)

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:《C语言入门知识》&#x1f649; &#x1f649; 内容推荐:&#x1f649; &#x1f439;今日诗词:十年花骨东风泪&#xff0c;几点螺香素壁尘&#x1f439; 目录 &#x1f338;思维导图&#x1f338; &#x1f338;…

怎么做加密文件二维码?简单技巧快速做二维码

怎么做一个加密的文件二维码呢&#xff1f;现在将文件做成二维码来传递是一种很常见的方式&#xff0c;那么为了保证文件不会被私自下载&#xff0c;那么如何在将文件生成二维码的时候&#xff0c;给二维码进行加密设置呢&#xff1f;下面就让小编给大家分享一下二维码生成器在…

【C\C++】内存分配 和 动态内存管理方式

文章目录 内存分类题目&#xff1a;知识巩固选择题: 变量位于内存中的位置计算题 变量值的大小 答案 C语言 动态内存管理malloc / calloc / realloc作用区别 C 内存管理方式operator new 与 operator deletenew 与 delete 的实现原理malloc free 与 new delete 的区别 内存泄漏…