分类目录归档:第三方库

西门子PLC snap7

一. 需求
集成snap7库,对西门子plc进行访问。
二. 步骤

  1. 下载snap7 库,之后取build/bin/win64 下的 dll 和lib 直接使用。
    https://sourceforge.net/projects/snap7/files/1.4.2/

2. 把examples/cpp 下的 snap7.h 和cpp 加到工程里。

3. 封装一个单例 来使用 这个库。

#ifndef PLCMANAGER_H
#define PLCMANAGER_H

#include <QObject>
#include <snap7.h>

class PLCManager : public QObject
{
    Q_OBJECT
public:
    static PLCManager& getInstance() {
        static PLCManager instance;
        return instance;
    }
    PLCManager(const PLCManager&) = delete;
    PLCManager& operator=(const PLCManager&) = delete;

    //连接plc
    int connect(QString ip);
    //设置转台角度
    int setTurntableAngle(float value);

private:
    explicit PLCManager(QObject *parent = nullptr);
    ~PLCManager();

signals:

private:
    std::shared_ptr<TS7Client> client;
    bool bConnected{false};
};

#endif // PLCMANAGER_H
#include "plcmanager.h"

PLCManager::PLCManager(QObject *parent)
    : QObject{parent}
{
    client = std::make_shared<TS7Client>();
}

PLCManager::~PLCManager()
{

}

int PLCManager::connect(QString ip)
{
    int ret = client->ConnectTo(ip.toStdString().data(),0,1);
    if(ret==0){
        bConnected=true;
    }else{
        bConnected=false;
    }
    return ret;
}

int PLCManager::setTurntableAngle(float value)
{
    if(!bConnected)
        return -1;

    //测试
    byte bufferWrite[1]={1};

    client->MBWrite(100,1,&bufferWrite);
}

4. 使用的时候 直接调用业务函数。

   PLCManager::getInstance().connect("127.0.0.1");
   PLCManager::getInstance().setTurntableAngle(0);

5. 用Hsl工具测试下,这里再M100 位置 写入了一个byte ,值为1。

6,常用读写

void PLCManager::writeInt(int DBNumber, int StartByte, short value)
{
    if(!bConnected)
        return;
    uint8_t  bytes[2] = {0};
    std::memcpy(bytes, &value, sizeof(value));
    std::reverse(bytes, bytes + sizeof(bytes));
    client->WriteArea(Area,DBNumber,StartByte,2,S7WLByte,bytes);
}

short PLCManager::readInt(int DBNumber, int StartByte)
{
    if(!bConnected)
        return -1;

    uint8_t buffRead[2];
    int ret = client->ReadArea(Area,DBNumber,StartByte,2,S7WLByte,&buffRead);
    if(ret==0){
        short intValue = static_cast<int>((buffRead[1] & 0xFF) |
                                          ((buffRead[0] & 0xFF) << 8));

        return intValue;
    }else{
        return -1;
    }
}

void PLCManager::writeFloat(int DBNumber, int StartByte, float value)
{
    if(!bConnected)
        return;

    uint8_t  bytes[4] = {0};
    std::memcpy(bytes, &value, sizeof(value));
    std::reverse(bytes, bytes + sizeof(bytes));

    client->WriteArea(Area,DBNumber,StartByte,4,S7WLByte,bytes);
}

float PLCManager::readFloat(int DBNumber, int StartByte)
{
    if(!bConnected)
        return -1;

    uint8_t buffRead[4];

    int ret = client->ReadArea(Area,DBNumber,StartByte, 4,S7WLByte,buffRead);
    if(ret==0){
        uint32_t value = (static_cast<uint32_t>(buffRead[0]) << 24) |
                         (static_cast<uint32_t>(buffRead[1]) << 16) |
                         (static_cast<uint32_t>(buffRead[2]) << 8) |
                         static_cast<uint32_t>(buffRead[3]);

        float floatValue;
        std::memcpy(&floatValue, &value, sizeof(float));
        return floatValue;
    }else{
        return -1;
    }

}

void PLCManager::writeBool(int DBNumber, int StartByte, int StartBit, bool value)
{
    if(!bConnected)
        return;

    byte res[1]{0};
    int ret = client->ReadArea(Area,DBNumber,StartByte,1,S7WLByte,&res);
    if(ret==0){
        byte result[1]{0};
        if(!value){
            unsigned char mask = ~(1 << StartBit);
            result[0] = res[0] & mask;
        }else{
            unsigned char mask = 1 << StartBit;
            result[0] = res[0] | mask;
        }
        client->WriteArea(Area, DBNumber, StartByte, 1, S7WLByte, &result);
    }
}

bool PLCManager::readBool(int DBNumber, int StartByte, int StartBit)
{
    if(!bConnected)
        return -1;

    byte res[1]{0};
    //读一个字节 8位
    int ret = client->ReadArea(Area,DBNumber,StartByte,1,S7WLByte,&res);
    if(ret==0){
        //判断第StartBit位
        unsigned char mask = 1 << StartBit;
        unsigned char result = res[0] & mask;
        if(result!=0){
            return true;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

模块间通信

一,描述

Qt 通常使用信号和槽,进行模块间通信,但是绑定的时候,必须要有一个地方,同时知道信号和槽的所属对象,耦合性还是太紧密,这个库,只要按照规定的写法,就可以实现信号和槽的通信,并且不需要绑定,减少了一层耦合。

二,使用

  • A B两个类都需要包含头文件。#include PSEventController.h

  • A类中触发的地方,调用publish函数。

//比如按下按钮 ,需提供一个唯一的标识字符串,可自定义参数
PSEventController::publish(“addLine”,Q_ARG(bool,isChecked));

  • B类中 定义 on_psEvent_xxx 函数,并实现。

//此处的addLine 即为上述pulish 函数中的第一个参数,isChecked 为publis中的第二个参数
void on_psEvent_addLine(bool isChecked);

  • B类中初始化的时候 执行 subscribe 函数。

PSEventController::subscribe(this,addLine);

这样,A 和B 两个类 发布和订阅的唯一字符串标识符只要一致,A 在pubsh 的时候 B 就可以subscribe到,并且A 和B 完全耦合,相互不可见。

三,库 具体内容

//PSEventController.h
#ifndef PSEVENTCONTROLLER_H
#define PSEVENTCONTROLLER_H

#include <QObject>
#include <QReadWriteLock>
#include <QMap>
#include <QList>

#define METHOD_PREFIX on_psEvent_

class PSEventController : public QObject
{
    Q_OBJECT
public:
    static void unSubscribe(QObject* listener, const QByteArray& eventName);
    static bool subscribe(QObject* listener, const QByteArray& eventName);
    static bool publish(const QByteArray& eventName, Qt::ConnectionType connectionType,
        QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(),
        QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(),
        QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(),
        QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(),
        QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());

    static inline bool publish(const QByteArray& eventName,
        QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(),
        QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(),
        QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(),
        QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(),
        QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument())
    {
        return publish(eventName, Qt::AutoConnection, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
    }

    static inline QString get_Errors()
    {
        return ps_LastError_;
    }

    static inline void clearEvents()
    {
        QWriteLocker locker(&ps_Lock_);
        psEvents_pool_.clear();
    }

    static inline QByteArray methodFormatting(const QByteArray& eventName) {
        return METHOD_PREFIX + eventName;
    }
private:
    static QMap<QByteArray, QList<QObject*>> psEvents_pool_;
    static QReadWriteLock ps_Lock_;

    static QString ps_LastError_;
};

#endif // PSEVENTCONTROLLER_H
//PSEventController.cpp
#include PSEventController.h
#include <QWriteLocker>

QMap<QByteArray, QList<QObject*>> PSEventController::psEvents_pool_;
QReadWriteLock PSEventController::ps_Lock_;
QString PSEventController::ps_LastError_;

void PSEventController::unSubscribe(QObject* listener, const QByteArray& eventName)
{
    QWriteLocker locker(&ps_Lock_);
    int index = -1;
    if (psEvents_pool_.contains(eventName) &&
        (index = psEvents_pool_[eventName].indexOf(listener)) >= 0 && index < psEvents_pool_[eventName].count())
        psEvents_pool_[eventName].takeAt(index);
}

bool PSEventController::subscribe(QObject* listener, const QByteArray& eventName)
{
    QWriteLocker locker(&ps_Lock_);
    if (psEvents_pool_.contains(eventName)) {
        if (-1 != psEvents_pool_[eventName].indexOf(listener)) {
            ps_LastError_ = QString(This object is subscribed to this eventName);
            return false;
        }
        psEvents_pool_[eventName].push_back(listener);
        return true;
    } else {
        psEvents_pool_.insert(eventName, { listener });
        return true;
    }
}

bool PSEventController::publish(const QByteArray& eventName, Qt::ConnectionType connectionType,
    QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3,
    QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7,
    QGenericArgument val8, QGenericArgument val9)
{
    QReadLocker locker(&ps_Lock_);
    if (!psEvents_pool_.contains(eventName)) {
        ps_LastError_ = QString(No objects subscribe to this eventName);
        return false;
    }
    auto methodName = methodFormatting(eventName);
    QStringList errors;
    for (auto listener : psEvents_pool_[eventName]) {
        if (!listener)
            continue;
        auto ret = QMetaObject::invokeMethod(listener, methodName, connectionType,
            val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
        if (!ret)
            errors.append(QString(%1:%2).arg(listener->metaObject()->className()).arg(listener->objectName()));
    }
    if (errors.isEmpty())
        return true;
    ps_LastError_ = QString(%1 execution failed:[\n).arg(QString(eventName));
    for (auto& err : errors)
        ps_LastError_ += QString(%1;\n).arg(err);
    ps_LastError_ += ]\n;
    return false;
}

mqtt

一, 需求
Qt使用mqtt协议与服务端进行通信。

二,使用

  1. 所有源码加入到工程里,我比较喜欢源码引用,这样在跨平台时,省去了路径的配置。
    https://github.com/emqx/qmqtt
  2. 写一个单例,建立连接,并在连接成功时,订阅 主题。
#ifndef MQTTMANAGERT_H
#define MQTTMANAGERT_H

#include <iostream>
#include <mqtt/qmqtt.h>

class MQTTManager:public QObject
{
    Q_OBJECT
public:
    static MQTTManager* instance();

    void setInTemperature(QString newInTemperature);

private:
    ~MQTTManager();
    MQTTManager();

public slots:
    void doConnected();
    void onSubscribed(const QString& topic);
    void doDisconnected();
    void doDataReceived(QMQTT::Message message);

signals:
    void inTemperatureChanged();

private:
    //mqtt客户端
    QMQTT::Client *client;
    static MQTTManager *manager;

    //室内温度
    QString inTemperature;

};
#endif // MQTTMANAGERT_H
#include "mqttManagert.h"
MQTTManager* MQTTManager::manager=nullptr;
const QString HOST= "127.0.0.1";

//温度和湿度
const QString TOPIC_IN_TEMPERATURE_HUMIDITY ="qmqtt/in_temperature_humidity";

MQTTManager::MQTTManager(){
    client = new QMQTT::Client(QHostAddress(HOST),1883,this);
    connect(client,&QMQTT::Client::connected,this,&MQTTManager::doConnected);
    connect(client,&QMQTT::Client::disconnected,this,&MQTTManager::doDisconnected);
    connect(client,&QMQTT::Client::received,this,&MQTTManager::doDataReceived);
    connect(client, &QMQTT::Client::subscribed, this, &MQTTManager::onSubscribed);

    client->connectToHost();
}

MQTTManager::~MQTTManager(){
    client->disconnected();
}

MQTTManager* MQTTManager::instance(){
    if(!manager){
        manager = new MQTTManager();
    }
    return manager;
}

void MQTTManager::doConnected(){
    qDebug()<<"doConnected ok";
    client->subscribe(TOPIC_IN_TEMPERATURE_HUMIDITY);
}
void MQTTManager::onSubscribed(const QString& topic)
{
    qDebug() << "onSubscribed " << topic;
}

//解析数据
void MQTTManager::doDataReceived(QMQTT::Message message){
    QString mes = QString(message.id())+" "+QString(message.qos())+" "+message.topic()+" "+message.payload()+"\n";
    if(message.topic()==TOPIC_IN_TEMPERATURE_HUMIDITY){
        QStringList dataList = QString(message.payload()).split(",");
        if(dataList.size()==2){
            setInTemperature(dataList[0]);
        }
    }
}

void MQTTManager::doDisconnected(){
    qDebug()<<"doDisconnected ok";
}

void MQTTManager::setInTemperature(QString newInTemperature)
{
    if (inTemperature == newInTemperature)
        return;
    inTemperature = newInTemperature;
    emit inTemperatureChanged();
}
  1. 发布主题,直接调用publish函数
    publish(const QMQTT::Message& message)

http

一,需求
Qt调用http接口,如果使用本身的network库需要做大量的工作,采用httplib这个库,则调用起来就特别优雅。

二,使用

  1. 这个库就是一个头文件,直接include进去即可。
    https://github.com/yhirose/cpp-httplib

  2. 看一个客户端列子。

    #include <httplib.h>
    #include <iostream>
    int main(void)
    {
    //定义客户端
    httplib::Client cli("localhost", 1234);
    
    //调用get接口
    if (auto res = cli.Get("/hi")) {
    if (res->status == StatusCode::OK_200) {
      std::cout << res->body << std::endl;
    }
    } else {
    auto err = res.error();
    std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
    }
    }

  3. 服务端写起来也很方便。

    #include <httplib.h>
    int main(void)
    {
    using namespace httplib;
    
    Server svr;
    
    svr.Get("/hi", [](const Request& req, Response& res) {
    res.set_content("Hello World!", "text/plain");
    });
    
    // Match the request path against a regular expression
    // and extract its captures
    svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
    auto numbers = req.matches[1];
    res.set_content(numbers, "text/plain");
    });
    
    // Capture the second segment of the request path as "id" path param
    svr.Get("/users/:id", [&](const Request& req, Response& res) {
    auto user_id = req.path_params.at("id");
    res.set_content(user_id, "text/plain");
    });
    
    // Extract values from HTTP headers and URL query params
    svr.Get("/body-header-param", [](const Request& req, Response& res) {
    if (req.has_header("Content-Length")) {
      auto val = req.get_header_value("Content-Length");
    }
    if (req.has_param("key")) {
      auto val = req.get_param_value("key");
    }
    res.set_content(req.body, "text/plain");
    });
    
    svr.Get("/stop", [&](const Request& req, Response& res) {
    svr.stop();
    });
    
    svr.listen("localhost", 1234);
    }
//客户端传图例子
QString file = QCoreApplication::applicationDirPath() + "/hf.jpg";
httplib::Client cli(ip, port);
cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds

std::ifstream sfile(file.toStdString().c_str(), std::ios::binary);
std::string imageData((std::istreambuf_iterator<char>(sfile)), std::istreambuf_iterator<char>());
httplib::MultipartFormDataItems items = {
{"file", imageData,  "hf.jpg", "application/octet-stream"},
};
auto res = cli.Post("/infer", items);
if (res&&res->status == 200)
{
		std::string body = res->body;
		std::cout << "上传成功: " << body;
}

json

一, 需求
Qt本身的json 用起来很麻烦,建议选用nlohmann 这个库来对json格式进行处理。

二, 使用

  1. 个人倾向于直接将源码放到目录下,然后直接引用头文件。
    https://github.com/nlohmann/json
#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;

std::ifstream f("example.json");
json data = json::parse(f);
  1. 这个库有个好处,就是可以将结构体或者类序列化和反序列化,直接转成json,或者由json生成。

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// 定义一个结构体
struct Person {
    std::string name;
    int age;
    std::string address;

    // 反序列化方法,将 JSON 对象解析为结构体
    static Person from_json(const json& j) {
        Person person;
        person.name = j.at("name").get<std::string>();
        person.age = j.at("age").get<int>();
        person.address = j.at("address").get<std::string>();
        return person;
    }
};

int main() {
    // JSON 字符串,包含 Person 数据
    std::string json_str = R"(
        {
            "name": "John Doe",
            "age": 30,
            "address": "123 Main St, City"
        }
    )";

    // 解析 JSON 字符串
    json j = json::parse(json_str);

    // 将 JSON 反序列化为 Person 结构体
    Person person = Person::from_json(j);

    // 打印反序列化后的 Person 对象
    std::cout << "Name: " << person.name << std::endl;
    std::cout << "Age: " << person.age << std::endl;
    std::cout << "Address: " << person.address << std::endl;

    return 0;
}
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// 定义一个结构体
struct Person {
    std::string name;
    int age;
    std::string address;

    // 将结构体序列化为 JSON 对象
    json to_json() const {
        return {
            {"name", name},
            {"age", age},
            {"address", address}
        };
    }
};

int main() {
    // 创建一个 Person 对象
    Person person;
    person.name = "John Doe";
    person.age = 30;
    person.address = "123 Main St, City";

    // 将结构体序列化为 JSON 对象
    json j = person.to_json();

    // 打印序列化后的 JSON 对象
    std::cout << j.dump(4) << std::endl;  // 使用 4 个空格缩进格式化输出

    return 0;
}
  1. 解析一个嵌套的json
    
    //json 长这样
    [
    {
        "type": "abc",
        "template":"1.stl",
        "dataList": [
            {
                "angle": 30,
                "track": "track1"
            },
            {
                "angle": 30,
                "track": "track1"
            },{
                "angle": 30,
                "track": "track1"
            }
        ]
    },
    {
        "type": "cde",
        "template":"2.stl",
        "dataList": [
            {
                "angle": 30,
                "track": "track1"
            },
            {
                "angle": 30,
                "track": "track1"
            },{
                "angle": 30,
                "track": "track1"
            }
        ]
    }
    ]
//读取
//定义结构体
struct Data{
    //产品型号
    QString type;
    //模板文件
    QString templateFile;
    //转台角度 对应的轨迹
    QMap angleToTrack;
};

//数据
QList allData;

//解析
void Widget::initData()
{
    //读取json文件
    QString filePath = QCoreApplication::applicationDirPath()+"/data.json";
    QFile file(filePath);
    if(file.exists()){
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug() << "Failed to open file:" << file.errorString();
            return;
        }

        QTextStream in(&file);
        QString content = in.readAll();

        json li = json::parse(content.toStdString());
        allData.clear();
        for (const auto& item : li) {
            Data data;
            std::string type = item["type"];
            std::string templateFile = item["template"];
            data.type = QString::fromStdString(type);
            data.templateFile = QString::fromStdString(templateFile);

            json dataList = item["dataList"];
            for (const auto& obj : dataList) {
                QMap angleToTrackMap;
                float angle =  obj["angle"];
                std::string track = obj["track"];
                angleToTrackMap.insert(angle,QString::fromStdString(track));
                data.angleToTrack = angleToTrackMap;
            }
            allData.append(data);
        }

        file.close();
    }
}