首页 > 其他分享 >modbus开源库 libmodbus

modbus开源库 libmodbus

时间:2024-01-15 19:14:42浏览次数:31  
标签:libmodbus return int void CManageModbus mapping modbus 开源

近期用到了modbus,搜到了libmodbus开源库,记录一下。网上关于如何编译的文章很多,不再赘述。

借鉴:https://blog.csdn.net/qq_38158479/article/details/120928043

说明:本文代码改造为适于windows的qt。

为了多线程业务处理,类继承QObject,这里作为服务器(子站)。代码和例子位于文末。例子和测试软件下载

说明一下个人对代码中寄存器首地址和数量的理解。

在类cmanagemodbus(tcp服务器)中,以线圈举例:_modbus_mapping_t中,初始化线圈元素个数(nb_bits)是60000,如果首地址(start_bits)是1024,占用元素数量是100个,则线圈数组的数据应该从tab_bits[1024]开始写入,到tab_bits[1123]结束。

我的代码例子中代码很多不完善,而且没有对收到的数据进行解析,是否需要解析,根据个人需求。

头文件:

#ifndef CMANAGEMODBUS_H
#define CMANAGEMODBUS_H
#include <iostream>
#include <thread>
#include <stdlib.h>
#include <iostream>
#include <mutex>
#include <string>
#include <QObject>
#include <QThread>

/*如果是windows平台则要加载相应的静态库和头文件*/
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
#include "libmodbus/modbus.h"
#pragma comment(lib, "Ws2_32.lib")
//#pragma comment/*(lib, "modbus.lib")*/
/*linux平台*/
#else
#include <modbus/modbus.h>
#include <unistd.h>
#include <error.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#endif

using namespace std;

//#define  MAX_POINT  50000

//coil status 线圈寄存器 使用功能码:01H(读);05H(写单个位);0FH(写多个位)
//input status 离散输入寄存器 :02H
//holding register 保持寄存器 :03H(读);06H(写单个字节);0FH(写多个字节)
//input register 输入寄存器:04H

enum DataType
{
    TypeYxData = 0,
    TypeYcData,
    TypeYtData,
    TypeYkData,
    TypeYmData,
    TypeUnknown
};

#pragma pack(1)
//配置信息
struct S_ModbusConfigInfo
{
    int slaveId;//子站地址
    int yxFuncCode;//遥信功能码 规定为02
    int ycFuncCode;//遥测功能码 规定为03
    int yxStartAddr;//遥信首地址
    int ycStartAddr;//遥测首地址
    int ymStartAddr;//遥脉首地址
    int yxNum;//遥信数量
    int ycNum;//遥测数量
    int ymNum;//遥脉数量
    S_ModbusConfigInfo()
    {
        yxFuncCode = 0x02;
        ycFuncCode = 0x03;
    }
};
#pragma pack()

Q_DECLARE_METATYPE(S_ModbusConfigInfo) //注册结构体

class CManageModbus : public QObject
{
    Q_OBJECT

public:
    explicit CManageModbus(string host="0.0.0.0", uint16_t port=502, bool bServer = true, bool bRemoveHead = true, QObject* parent = nullptr);//bServer 启动的是否是服务器
    ~CManageModbus();

public:
    void recieveMessages();//收取信息
    bool modbus_set_slave_id(int id);//设置设备识别码id
    bool initModbus(std::string Host_Ip, int port, bool debugging);//初始化modbus
    
    uint8_t getTab_Input_Bits(int NumBit);
    bool setTab_Input_Bits(int NumBit, uint8_t Value);//向首地址写入 寄存器写入

    uint16_t getHoldingRegisterValue(int registerNumber);
    float getHoldingRegisterFloatValue(int registerStartaddress);
    
    bool setHoldingRegisterValue(int registerNumber, uint16_t Value);
    bool setHoldingRegisterValue(int registerNumber, float Value);

    bool setInputRegisterValue(int registerNumber, uint16_t Value);
    bool setInputRegisterValue(int registerNumber, float Value);
    void closeModbus();//关闭modbus
    void stopThread();//退出线程
    void getConfigInfo(S_ModbusConfigInfo &configInfo);//设置界面配置信息,服务器开始时,收到信号更新配置信息
    void getTableData(int nDataType, uint16_t *pData, int nDataLen);
    void getTableData(int nDataType, uint8_t *pData, int nDataLen);//表格数据写入到mapping
signals:
    void sigAddLog(bool bServer, bool bRecv, QString msg);
public slots:
    void doWork();//线程函数


private:
    std::mutex slavemutex;
    int m_errCount{ 0 };
    int m_modbusSocket{ -1 };
    bool m_initialized{ false };
    modbus_t* ctx{ nullptr };//modbus 实例
    modbus_mapping_t* mapping{ nullptr };
    /*Mapping*/
    int m_numBits{ 60000 };//线圈, (数组元素个数)
    int m_numInputBits{ 60000 };//离散输入, (数组元素个数)
    int m_numRegisters{ 60000 };//输入寄存器(数组元素个数)
    int m_numInputRegisters{ 60000 };// 保持寄存器个数(数组元素个数)
    bool m_bStopThread;//控制线程退出
    bool m_bServer;//是否服务器
    bool m_bRemoveHead;//tcp模式去掉协议头(只保留装置地址,和peugeot.exe保持一致)
    S_ModbusConfigInfo m_configInfo;
private:
    void loadFromConfigFile();
    void run();
    QString unchar2QString(unsigned char* pIn, int nLen);//unsingned char 转16进制字符串
    int QStringHex2Char(const QString& strIn, uint8_t *out, int lenSpace);//16禁止字符串转unsigned char,QString中必须是类似"16 05 78","160578",5必须补为05
    void addMBAPhead(uint8_t *query, int& rc);//接收到无报文头的,加上
    void removeMBAPhead(uint8_t *query, int& rc);//发送时,再去掉报文头
    void buildSendMapping(const uint8_t *req, int req_len);
    void buildYcMapping(uint16_t *pData, int nDataLen);//遥测对应 0x03
    void buildYxMapping(uint8_t *pData, int nDataLen);//目前遥信对应 0x02
    void buildYkMapping(uint8_t *pData, int nDataLen);//向map
    void buildYtMapping(uint8_t *pData, int nDataLen);
    void buildYmMapping(uint8_t *pData, int nDataLen);
    unsigned int ModBusCRC16(unsigned char *data, unsigned int len);
};
/*annotation:
(1)https://www.jianshu.com/p/0ed380fa39eb
(2)typedef struct _modbus_mapping_t
{
    int nb_bits;                //线圈
    int start_bits;
    int nb_input_bits;          //离散输入
    int start_input_bits;
    int nb_input_registers;     //输入寄存器
    int start_input_registers;
    int nb_registers;           //保持寄存器
    int start_registers;
    uint8_t *tab_bits;
    uint8_t *tab_input_bits;
    uint16_t *tab_input_registers;
    uint16_t *tab_registers;
}modbus_mapping_t;*/
#endif // CMANAGEMODBUS_H
View Code

源文件:

#include "cmanagemodbus.h"
#include <string.h>
#include <QDebug>

#ifdef WIN32
typedef int socklen_t;
#endif

/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      modbus initialization
 * @param      IP/PORT/debugflag
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
bool CManageModbus::initModbus(std::string Host_Ip = "127.0.0.1", int port = 502, bool debugging = true)
{
    ctx = modbus_new_tcp(Host_Ip.c_str(), port);
    //modbus_set_debug(ctx, debugging);
    if (ctx == NULL)
    {
        fprintf(stderr, "There was an error allocating the modbus\n");
        throw - 1;
    }
    m_modbusSocket = modbus_tcp_listen(ctx, 1);
    /*设置线圈, 离散输入, 输入寄存器, 保持寄存器个数(数组元素个数))*/
    mapping = modbus_mapping_new(m_numBits, m_numInputBits, m_numInputRegisters, m_numRegisters);
    if (mapping == NULL)
    {
        fprintf(stderr, "Unable to assign mapping:%s\n", modbus_strerror(errno));
        modbus_free(ctx);
        m_initialized = false;
        return false;
    }
    m_initialized = true;
    return true;
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      Constructor
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
CManageModbus::CManageModbus(string host, uint16_t port, bool bServer, bool bRemoveHead, QObject* parent)
    :QObject(parent)
{
    m_bServer = bServer;
    m_bStopThread = false;
    m_bRemoveHead = bRemoveHead;
    initModbus(host, port, false);

    //TODO:
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      Destructor
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
CManageModbus::~CManageModbus()
{
    if(!ctx)
    {
        modbus_mapping_free(mapping);
        modbus_close(ctx);
        modbus_free(ctx);
    }
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      loadFromConfigFile
 * @version    v1
 * @return     null
 * @date       2021/10/18
 **************************************************************/
void CManageModbus::loadFromConfigFile()
{
    return;
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      run
 * @version    v1
 * @return     null
 * @date       2021/10/18
 **************************************************************/
void CManageModbus::run()
{
    std::thread loop([this]()
    {
        while (true)
        {
            if (m_initialized)
            {
                recieveMessages();
            }
            else
            {
                m_initialized = true;
            }
        }
    });
    loop.detach();
    return;
}

/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      modbus_set_slave_id
 * @param      id
 * @version    v1
 * @return     null
 * @date       2021/10/19
 **************************************************************/
bool CManageModbus::modbus_set_slave_id(int id)
{
    int rc = modbus_set_slave(ctx, id);
    if (rc == -1)
    {
        fprintf(stderr, "Invalid slave id\n");
        modbus_free(ctx);
        return false;
    }
    return true;
}


bool CManageModbus::setInputRegisterValue(int registerStartaddress, uint16_t Value)
{
    if (registerStartaddress > (m_numRegisters - 1))
    {
        return false;
    }
    slavemutex.lock();
    mapping->tab_input_registers[registerStartaddress] = Value;
    slavemutex.unlock();
    return true;
}





/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      setRegisterValue(设置保存寄存器的值,类型为uint16_t)
 * @param      registerStartaddress(保存寄存器的起始地址)
 * @param      Value(写入到保存寄存器里的值)
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
bool CManageModbus::setHoldingRegisterValue(int registerStartaddress, uint16_t Value)
{
    if (registerStartaddress > (m_numRegisters - 1))
    {
        return false;
    }
    slavemutex.lock();
    mapping->tab_registers[registerStartaddress] = Value;
    slavemutex.unlock();
    return true;
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      getRegisterValue(获取保存寄存器的值)
 * @param      registerStartaddress(保存寄存器的起始地址)
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
uint16_t CManageModbus::getHoldingRegisterValue(int registerStartaddress)
{
    if (!m_initialized)
    {
        return -1;
    }
    return mapping->tab_registers[registerStartaddress];
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      setTab_Input_Bits(设置输入寄存器某一位的值)
 * @param      NumBit(输入寄存器的起始地址)
 * @param      Value(输入寄存器的值)
 * @version    v1
 * @return     null
 * @date       2021/10/8
 **************************************************************/
bool CManageModbus::setTab_Input_Bits(int NumBit, uint8_t Value)
{
    if (NumBit > (m_numInputBits - 1))
    {
        return false;
    }
    slavemutex.lock();
    mapping->tab_input_bits[NumBit] = Value;
    slavemutex.unlock();
    return true;
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      getTab_Input_Bits(获取输入寄存器某一位的值)
 * @param      NumBit(输入寄存器相应的bit位)
 * @version    v1
 * @return     null
 * @date       2021/10/8
 **************************************************************/
uint8_t CManageModbus::getTab_Input_Bits(int NumBit)
{
    if (!m_initialized)
    {
        return -1;
    }
    return mapping->tab_input_bits[NumBit];
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      setRegisterFloatValue(设置浮点值)
 * @param      (Value:浮点值,registerStartaddress寄存器起始地址)
 * @version    v1
 * @return     null
 * @date       2021/10/8
 **************************************************************/
bool CManageModbus::setHoldingRegisterValue(int registerStartaddress, float Value)
{
    if (registerStartaddress > (m_numRegisters - 2))
    {
        return false;
    }
    /*小端模式*/
    slavemutex.lock();
    modbus_set_float(Value, &mapping->tab_registers[registerStartaddress]);
    slavemutex.unlock();
    return true;
}


bool CManageModbus::setInputRegisterValue(int registerStartaddress, float Value)
{
    if (registerStartaddress > (m_numRegisters - 2))
    {
        return false;
    }
    /*小端模式*/
    slavemutex.lock();
    modbus_set_float(Value, &mapping->tab_input_registers[registerStartaddress]);
    slavemutex.unlock();
    return true;
}

void CManageModbus::closeModbus()
{
    closesocket(m_modbusSocket);
    modbus_mapping_free(mapping);
    modbus_close(ctx);
    modbus_free(ctx);
    ctx = nullptr;
}

void CManageModbus::stopThread()
{
    m_bStopThread = true;
}


void CManageModbus::doWork()
{
    while (!m_bStopThread)
    {
        if (m_initialized)
        {
            recieveMessages();
        }
        else
        {
            m_initialized = true;
        }
    }


    int i = 1;
}

/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      获取寄存器里的浮点数 
 * @param      registerStartaddress寄存器起始地址
 * @version    v1
 * @return     两个uint16_t拼接而成的浮点值
 * @date       2021/10/6
 **************************************************************/
float CManageModbus::getHoldingRegisterFloatValue(int registerStartaddress)
{
    if (!m_initialized)
    {
        return -1.0f;
    }
    return modbus_get_float_badc(&mapping->tab_registers[registerStartaddress]);
}
/***************************************************************
 * @file       CManageModbus.cpp
 * @author     seer-txj
 * @brief      支持多个master同时连接
 * @version    v1
 * @return     null
 * @date       2021/10/6
 **************************************************************/
void CManageModbus::recieveMessages()
{
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    int master_socket;
    int rc;
    fd_set refset;
    fd_set rdset;
    /* Maximum file descriptor number */
    int fdmax;
    /* Clear the reference set of socket */
    FD_ZERO(&refset);
    /* Add the server socket */
    FD_SET(m_modbusSocket, &refset);

    /* Keep track of the max file descriptor */
    fdmax = m_modbusSocket;
    int nCount = 0;
    QString msg;

    while( true ) 
    {
        nCount++;
        rdset = refset;
        if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1) 
        {
            perror("Server select() failure.");
            break;
        }

        /* Run through the existing connections looking for data to be
         * read */
        for (master_socket = 0; master_socket <= fdmax; master_socket++) 
        {
            if (!FD_ISSET(master_socket, &rdset)) 
            {
                continue;
            }


            //qDebug() << "fdmax = " << fdmax << "master_socket = " << master_socket;

            if (master_socket == m_modbusSocket) 
            {
                /* A client is asking a new connection */
                socklen_t addrlen;
                struct sockaddr_in clientaddr;
                int newfd;

                /* Handle new connections */
                addrlen = sizeof(clientaddr);
                memset(&clientaddr, 0, sizeof(clientaddr));
                newfd = accept(m_modbusSocket, (struct sockaddr *)&clientaddr, &addrlen);
                if (newfd == -1) 
                {
                    perror("Server accept() error");
                }
                else
                {
                    FD_SET(newfd, &refset);

                    if (newfd > fdmax) 
                    {
                        /* Keep track of the maximum */
                        fdmax = newfd;
                    }
                    printf("New connection from %s:%d on socket %d\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd);
                }
                msg = QString("New connection from %1:%2 on socket %3").arg(inet_ntoa(clientaddr.sin_addr)).arg(clientaddr.sin_port).arg(newfd);
                qDebug() << msg;
                emit sigAddLog(m_bServer, true, msg);
            }
            else
            {
                modbus_set_socket(ctx, master_socket);

                rc = modbus_receive(ctx, query);

                if (rc > 0) 
                {
                    msg = unchar2QString(query, rc);
                    emit sigAddLog(m_bServer, true, msg);//接收到的数据显示到列表

                    if(m_bRemoveHead)
                    {
                        addMBAPhead(query, rc);//添加头
                    }

//                    QString str = "01 02 05 00 00 00 00 00 A2 52";
//                    uint8_t* send = new uint8_t[str.size()];
//                    int len = QStringHex2Char(str, send, 1);
                    int nSendLen = 0;
                    uint8_t *pSend = new uint8_t[260];
                    memset(pSend, 0, 260);

                    int errcode = modbus_reply(ctx, query, rc, mapping, pSend, &nSendLen, m_bRemoveHead?1:0);
                    if(errcode == -1)
                    {
                        msg = "发送失败";
                    }
                    else
                    {
                        msg = unchar2QString(pSend, nSendLen);
                    }
                    delete[] pSend;
                    pSend = nullptr;

                    emit sigAddLog(m_bServer, false, msg);

                }
                else if (rc == -1)
                {
                    /* This example server in ended on connection closing or
                     * any errors. */
                    /* 20240111屏蔽
                    msg = QString("Connection closed on socket %1").arg(master_socket);
                    emit sigAddLog(m_bServer, false, msg);
#ifdef _WIN32
                    closesocket(master_socket);
#else
                    close(master_socket);
#endif
                    //Remove from reference set
                    FD_CLR(master_socket, &refset);

                    if (master_socket == fdmax)
                    {
                        fdmax--;
                    }
                    */
                }
            }
        }
    }
    qDebug() << "out recieve";
    m_initialized = false;
}


QString CManageModbus::unchar2QString(unsigned char *pIn, int nLen)
{
    QString temp;
    QString msg;
    int j = 0;
    while(j < nLen)
    {
       temp = QString("%1 ").arg((int)pIn[j], 2, 16, QLatin1Char('0'));
       msg.append(temp);
       j++;
    }
    return msg;
}

int CManageModbus::QStringHex2Char(const QString &strIn, uint8_t *out, int lenSpace)
{
    uint8_t* p = nullptr;
    if (strIn.length() < 2)
    {
        return 0;
    }
    int offset = lenSpace + 2;//每次偏移2+间隔字符长度
    int j = 0;
    for (int i = 0; i < strIn.length(); i += offset)
    {
        QString num = strIn.mid(i, 2);
        bool ok = false;
        out[j] = num.toUInt(&ok, 16);
        j++;
        if (!ok)
        {
            return 0;
        }
    }
    return j;
}

void CManageModbus::addMBAPhead(uint8_t *query, int& rc)
{
    memcpy(query + 6, query, rc);
    memset(query, 0, 6);
    rc += 6;
}

void CManageModbus::removeMBAPhead(uint8_t *query, int &rc)
{
    memcpy(query, query + 6, rc - 6);
    rc -= 6;
}

void CManageModbus::buildSendMapping(const uint8_t *req, int req_len)
{

}

void CManageModbus::getTableData(int nDataType, uint8_t *pData, int nDataLen)
{
    switch (nDataType) {
    case TypeYxData:
        buildYxMapping(pData, nDataLen);
        break;
    case TypeYcData:
        //buildYcMapping(pData, nDataLen);
        break;
    case TypeYtData:
        buildYtMapping(pData, nDataLen);
        break;
    case TypeYkData:
        buildYkMapping(pData, nDataLen);
        break;
    case TypeYmData:
        buildYxMapping(pData, nDataLen);
        break;
    default:
        break;
    }
}

void CManageModbus::getConfigInfo(S_ModbusConfigInfo &configInfo)
{
    modbus_set_slave_id(configInfo.slaveId);
    m_configInfo = configInfo;
}

void CManageModbus::getTableData(int nDataType, uint16_t *pData, int nDataLen)
{
    switch (nDataType) {
    case TypeYxData:
        //buildYxMapping(pData, nDataLen);
        break;
    case TypeYcData:
        buildYcMapping(pData, nDataLen);
        break;
    case TypeYtData:
        //buildYtMapping(pData, nDataLen);
        break;
    case TypeYkData:
        //buildYkMapping(pData, nDataLen);
        break;
    case TypeYmData:
        //buildYxMapping(pData, nDataLen);
        break;
    default:
        break;
    }
}

void CManageModbus::buildYcMapping(uint16_t *pData, int nDataLen)
{
    if(mapping != nullptr)
    {
        memcpy(mapping->tab_registers + m_configInfo.ycStartAddr, pData, nDataLen * 2);//由于是uint16,所以得*2
    }
    int i = 1;
}

void CManageModbus::buildYxMapping(uint8_t *pData, int nDataLen)
{
    if(mapping != nullptr)
    {
        memcpy(mapping->tab_input_bits + m_configInfo.yxStartAddr, pData, nDataLen);
    }
}

void CManageModbus::buildYkMapping(uint8_t *pData, int nDataLen)
{
    if(mapping != nullptr)
    {
        memcpy(mapping->tab_registers, pData, nDataLen);
    }
}

void CManageModbus::buildYtMapping(uint8_t *pData, int nDataLen)
{
    if(mapping != nullptr)
    {
        memcpy(mapping->tab_registers, pData, nDataLen);
    }
}

void CManageModbus::buildYmMapping(uint8_t *pData, int nDataLen)
{
    if(mapping != nullptr)
    {
        memcpy(mapping->tab_registers, pData, nDataLen);
    }
}

unsigned int CManageModbus::ModBusCRC16(unsigned char *data, unsigned int len)
{
    unsigned int i, j, tmp, CRC16;

    CRC16 = 0xFFFF;             //CRC寄存器初始值
    for (i = 0; i < len; i++)
    {
        CRC16 ^= data[i];
        for (j = 0; j < 8; j++)
        {
            tmp = (unsigned int)(CRC16 & 0x0001);
            CRC16 >>= 1;
            if (tmp == 1)
            {
                CRC16 ^= 0xA001;    //异或多项式
            }
        }
    }
/*根据需要对结果进行处理*/
//    data[i++] = (unsigned char) (CRC16 & 0x00FF);
//    data[i++] = (unsigned char) ((CRC16 & 0xFF00)>>8);
    return CRC16;
}
View Code

例子说明,我用到Peugeot.exe测试,这个软件的tcp模式也是串口帧传输的(有点反人类)。图中第6步注意服务器ip。设置好后,两个对话框都要点击 “确定”

 个人程序页面:

 

标签:libmodbus,return,int,void,CManageModbus,mapping,modbus,开源
From: https://www.cnblogs.com/warmlight/p/17966073

相关文章

  • k8s(Kubernetes)一种用于自动部署、扩展和管理容器化应用程序的开源平台
    K8s全称为Kubernetes,是一种用于自动部署、扩展和管理容器化应用程序的开源平台。作为云原生技术的核心组件之一,其提供了大量灵活的Kubernetes教程,帮助开发人员和运维团队轻松地构建、交付和扩展应用程序,从而更好地应对不断增长的云端需求。K8s的核心是一个主节点,它负责管理和协调......
  • 开源字符识别 OCR 引擎推荐
    开源字符识别OCR引擎推荐sea​现代支付架构部经理 Tesseract开源OCR引擎(主存储库) github地址 GitHub-tesseract-ocr/tesseract:TesseractOpenSourceOCREngine(mainrepository) 官方网址 Tesseractdocumentation Tesseract......
  • Terraform 开源分支 OpenTofu 正式发布
    Terraform开源分支OpenTofu正式发布来源:OSCHINA编辑: 白开水不加糖2024-01-1111:03:34 02023大语言模型技术报告.pdf 经过五十多名开发人员历时四个月的开发,Terraform开源分支OpenTofu现已正式发布,可供生产使用,为Terraform用户提供了一条直接的迁......
  • 开源的代名词「GitHub 热点速览」
    当开发者谈论开源时,通常会想到GitHub,它不仅仅是一个代码托管平台,更是一个汇聚了全球开发者的社交中心。过去,开发者发布一款软件后,都是在自己的小圈子里默默努力和交流,现在通过GitHub平台可以方便地与全球的开发者分享、交流和协作。贡献者在这里展示自己的才华,追随者在这里寻......
  • Solo 开发者周刊 (第 1 期):开源产品的探索之路
    产品推荐如何着手将一个简单的想法转变为一个成熟的开源项目,以及如何在此过程中利用和贡献于开源社区。同时使其达到商业化的同时,保持原有的开源精神。这些是我们需要探索的。Spug开源运维平台Spug是面向中小型企业设计的轻量级无Agent的自动化运维平台,整合了主机管理、主......
  • 50个常用的Qt开源库
    1.Qwt(https://qwt.sf.net):Qwt是一个基于Qt的数据可视化库,提供了绘制曲线、图表、仪表盘等功能。2.QJson(https://qjson.sourceforge.net):QJson是一个用于JSON数据解析和生成的库,使Qt应用程序能够方便地处理JSON格式的数据。3.QCustomPlot(https://www.qcustomplot.co......
  • Java使用modbus4j通过串口modbus-rtu协议 连接设备 demo
    前言项目中需要使用串口来连接操控烟雾报警器且只能使用modbus-rtu协议在找了一堆资料后终于成功了在此呈上代码和资料链接【ModBus】modbus之modbus4j的使用和流程原理解析(5)-CSDN博客使用modbus4j通过串口解析modbus协议(java)_javamodbus4j-CSDN博客 串口通讯需要使用modbus4j......
  • VB6的OfficeMenu控件 - 开源研究系列文章
          这次将原来VB6中喜欢和使用到的OfficeMenu的控件做一个使用介绍。      上次介绍了VB6中的控件引擎,但是那个只针对基本的控件,这个OfficeMenu控件在当时是收费的,笔者找度娘好不容易才下载到一个免费版本,而且使用起来也非常的方便,在当时那个年代是笔者对于VB6......
  • 我开源了一个 Go 学习仓库
    目录前言一、综述1.1HelloWord1.2命令行参数1.3查找重复行1.4GIF动画1.5获取一个URL1.6并发获取多个URL1.7实现一个Web服务器1.8杂项二、程序结构2.1名字2.2声明2.3变量2.4赋值2.5类型声明2.6包和文件2.7作用域三、基本数据类型3.1整数3.2浮点数3.3复数3.4......
  • [Bookmark]--立创开源
    开发板立创·GD32E230最小系统板无人机SuperUAVRGB征集令|物联网8x8炫彩随心屏"像素盒子"触摸屏LED灯板征集令|LED时钟小小光立方局域网控制的旋转LEDLINK自制ST-LINKV2-1(开源版本)/****************************************************//(C)COPYLEFT2018Mer......