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

2023-11-16

西门子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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 数论算法:唯一因子分解定理

    这里讲一下算法中常用到的唯一因子分解定理 合数a仅能以一种方式写成如下乘积形式 a p1 e1 p2 e2 pr er 其中pi为素数 p1
  • Vue基本知识1,安装创建以及常用指令

    Vue基本知识1 安装创建以及常用指令 1 Vue的概念 概念 vue是一个渐进式的JavaScript开发框架 基于MVVM实现数据驱动的框架 官网 还可以用来做SPA single page web application 一个网页就是
  • Qt udp数据发送太快,数据丢失

    Qt 在单线程中 如果数据发送太快 应用程序还在处理别的程序 就会触发不了readyRead信号 导致造成数据的丢失 解决方法可以将udp处理类继承于QThread 在多线程入口run函数里通过死循环接收数据 通过信号槽机制通知处理函数进行
  • 微信小程序&PHP 改变小程序码中间logo的方法

    需求 获取小程序码 小程序码中间logo换成用户头像 仔细看了小程序本身的程序 没有发现好的方法 如果有 不吝赐教呀 所以改变方法 把头像传回后台 使用 php gd库在后台操作 然后传回小程序端 初始数据准备 define PATH op
  • Hive的联级(cascade)- 修改分区表的字段类型或者增加新字段

    一 问题描述 踩坑 数仓的分区表 由于需求需要 要把int类型的字段改为bigint 我直接执行的以下语句 alter table table name change column 字段 字段 bigint 出现的问题 之后的分区数据可以正
  • 经济复苏!!! ReportLinker调整2020-2027 IMU市场预测金额至335亿美元

    2022年10月 市场研究机构ReportLinker发布了2020 2027全球IMU市场预测报告 报告预测到2027年全球IMU市场金额将达到236亿美元 2020 2027年期间复合增长率为5 4 新冠过后 经济复苏 ReportLi
  • Python同时显示多张图片在一个画面中(两种方法)

    很多时候需要把很多图片同时显示到一个画面中 现在分享两个方法 这里我恰好拿之前写的爬取网上图片保存到本地的爬虫模型爬一些图片作为素材Python 爬虫批量爬取网页图片保存到本地 得到素材如下所示 现在让这些图片同时显示 方法一 subplo
  • [论文笔记]知识图谱+推荐系统

    仅作个人笔记 2021 3 22 2021 3 29 1 RippleNet Propagating User Preferences on the Knowledge Graph for Recommender Systems 看到一篇翻
  • 深入理解程序设计使用Linux汇编语言

    关于函数 4 1 由系统提供的函数称为原函数 或源语 因为其他一切都是建立在它们之上 在汇编语言中 原语通常就是系统调用 4 3 由于内存的结构 栈是从内存顶部开始向下增长的 当我们提到 栈顶 请记住这是栈内存的底部 movl esp ea
  • C语言 - 制作一个可以容纳一千人的本地通讯录

    本章目录 前言 一 菜单制作 二 创建通讯录 1 创建人员信息结构体 2 创建完整通讯录结构体 3 初始化通讯录 4 存放数据 5 判断空间容量 三 各功能的实现 1 增加人员信息的功能 2 按名字查找的功能 内部使用 3 删除人员信息的功
  • 手把手教你CIFAR数据集可视化

    CIFAR数据集介绍与获取 如同从小到的父母教我们识别每个物体是什么一样 除了看到的画面 父母会在旁边告诉看到的画面是什么 这种学习方式叫做监督学习 与此对应还有无监督学习 计算机也一样 数据集通常应该至少包含两部分内容 一个是图像 一个是
  • C语言:三目运算符 “?”号

    三目运算符的表示一般为 该运算符连接3个对象 是C语言中唯一一个三目运算符 又称条件运算符 它的一般形式如下 表达式a 表达式b 表达式c 其执行步骤如下 1 计算表达式a的值 2 如果表达式a的值为1 则执行表达式b 3 如果表达式b的值
  • 支付宝API支付使用文档--java《扫一扫支付》--demo但是封装高可用--改一改配置就拿走用!超详细!!有手就行!!!--spring +vue-调用支付宝的当面付的预创建接口

    支付宝API支付使用文档 java 扫一扫支付 demo但是封装高可用 改一改配置就拿走用 超详细 有手就行 上 修改官网配置类 一单成的博客 CSDN博客 阿丹 上一篇文章具体的描述和讲解了官方提供的配置类 以及如何使用注册开发沙箱 本篇
  • Linux系统图形界面,字符界面切换快捷键。启动图形界面服务。

    Ctrl Alt F3 启动字符界面 Ctrl Alt F7 启动图形界面 启动图形界面服务 cd etc init d service lightdm restart
  • 重装win8.1搜索不到 wifi

    这几天一直忙着研究装系统 毕竟自己是个小白 经常搞到深夜4点钟 今天终于算是有点眉目了 重新装完win8 1 电脑竟然搜索不到wifi 点开右下角那个图标 只有宽带连接这一个选项 于是到网上搜索 怎么解决 网上给了很多答案 基本都差不多 我
  • TCP/IP的三次握手、四次挥手

    本文通过图来梳理TCP IP协议相关知识 TCP通信过程包括三个步骤 建立TCP连接通道 传输数据 断开TCP连接通道 如图1所示 给出了TCP通信过程的示意图 上图主要包括三部分 建立连接 传输数据 断开连接 建立TCP连接很简单 通过三
  • Temporary failure in name resolution解决方法

    终端运行sudo su 输密码 vi etc reslov conf 输入i进入编辑模式 在文档末尾加入 nameserver 8 8 8 8 nameserver 114 114 114 114 按esc 输入 wq保存退出 执行 etc
  • 第八章 Oracle恢复内部原理(重置日志RESETLOGS)

    重置日志选项用于下列情形后的第一次打开数据库的时候 不完全恢复 基于备份控制文件的恢复 CREATE CONTROLFILE RESETLOGS 重置日志的最主要的作用就是丢弃不完全恢复中没有使用的重做日志并保证后续的恢复不再需要 为此 重
  • dll修复工具哪个比较好?修复工具介绍

    DLL 动态链接库 是Windows操作系统中非常重要的一部分 它们存储了各种软件应用程序所需的公共代码和数据 然而 随着时间的推移 电脑上的DLL文件可能会因为各种原因而损坏或丢失 导致系统出现错误 因此 修复DLL错误是一项非常重要的任
  • linux 下 C++ 与三菱PLC 通过MC Qna3E 二进制 协议进行交互

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