五步十阁 - 编程
type: channel
link: http://8.129.33.146/index.php/category/编程
items: opendnp扩展之增加链路初始化报文
items: DNP3.0之杂谈
items: WorkEasierTooler 系列（五）读mdb（Access数据库文件）数据
items: MCCB调试工具
items: WorkEasierTooler 系列（四）xml文件解析和excel文件生成
items: WuuTool（二）字符编码转换
items: WuuTool（一）重拾旧梦
items: WorkEasierTooler 系列（三）101/104报文解析（续）
items: visual studio 2019/2022 与 vcpkg 的那些火花
items: WorkEasierTooler 系列（二）101/104报文解析

opendnp扩展之增加链路初始化报文
type: item
date: 2024-09-07T23:10:45+08:00
description: 一、前言原本一直使用的stepfunc/dnp3，但是库是rust开发的，再使用的到c的ffi，扩展起来太麻烦、太头疼了。最近因为新需求，就决定深入研究一下opendnp的代码，实现链路初始化的扩展。二、定位显而易见，类LinkContext维护了链路层的一个状态，包括FCB翻转、心跳测试、底层连接状态回调等等。发现如下函数，是在连接建立时触发的事件回调，因此我们增加链路初始化，可以考虑在这个函数体内部动刀。bool LinkContext::OnLowerLayerUp() { if (this->isOnline) { SIMPLE_LOG_BLOCK(logger, flags::ERR, "Layer already online"); return false; } this->isOnline = true; this->RestartKeepAliveTimer(); linkStatus = LinkStatus::UNRESET; //链路状态 listener->OnStateChange(linkStatus); upper->OnLowerLayerUp(); return true; }再根据LinkStatus前面的注释“Enumeration for reset/unreset states of a link layer”，搜索到相关的一个issue:LinkContext does incorrectly report LinkStatus as UNRESET on link up · Issue #432 · dnp3/opendnp3 · GitHub，翻阅后，加深对链路初始化的理解，也验证到定位确实没错。三、动刀1.链路初始化事件回调派生类增加在PriLinkLayerStates.h中增加如下类定义：class PLLS_ResetLinkStatusWait final : public PriStateBase { MACRO_STATE_SINGLETON_INSTANCE(PLLS_ResetLinkStatusWait); PriStateBase& OnAck(LinkContext& ctx, bool) override; PriStateBase& OnNack(LinkContext& ctx, bool) override; PriStateBase& OnLinkStatus(LinkContext& ctx, bool) override; PriStateBase& OnNotSupported(LinkContext& ctx, bool) override; PriStateBase& OnTxReady(LinkContext&) override; PriStateBase& OnTimeout(LinkContext&) override; };在PriLinkLayerStates.cpp中实现相关函数：PLLS_ResetLinkStatusWait PLLS_ResetLinkStatusWait::instance; PriStateBase& PLLS_ResetLinkStatusWait::OnAck(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnNack(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnLinkStatus(LinkContext& ctx, bool) { ctx.CancelTimer(); //ctx.CompleteKeepAlive(); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnNotSupported(LinkContext& ctx, bool) { ctx.CancelTimer(); ctx.FailResetLink(false); return PLLS_Idle::Instance(); } PriStateBase& PLLS_ResetLinkStatusWait::OnTxReady(LinkConte…

DNP3.0之杂谈
type: item
date: 2023-10-28T23:44:00+08:00
description: 最近在研究DNP3.0相关技术规范（IEEE 1815）以及开源库OPENDNP和stepfunc/dnp3，收获颇多，记录一二。一、智能指针在翻阅opendnp源码过程中，发现对外接口大量使用智能指针（unique_ptr 和 shared_ptr），并通过移动语义（std::move）进行构造。在接口测试和研究过程中，初步了解了智能指针的所有权：作用域。二、接口与Qt的信号与槽在将stepfunc/dnp3数据接口转Qt信号槽过程中，遇到如下问题：接口类都是通过unique_ptr来实现rust参数作用域（不太懂rust，姑且这么说吧），比如下述函数：/// @brief Add an association to the channel /// /// @param address DNP3 data-link address of the remote outstation /// @param config Association configuration /// @param read_handler Interface uses to load measurement data /// @param association_handler Association specific callbacks such as time synchronization /// @param association_information Association information interface /// @return Id of the association /// @throws ParamException AssociationId add_association(uint16_t address, const AssociationConfig& config, std::unique_ptr<ReadHandler> read_handler, std::unique_ptr<AssociationHandler> association_handler, std::unique_ptr<AssociationInformation> association_information);接口对象均是使用的std::unique_ptr，并在内部通过移动构造进行了所有权转移，导致接口调用的外部不再享有所有权，从而无法进行信号与槽的连接。为解决上述问题，新建一个单例类，纯用来触发各类内部接口信号，如下述代码所示：class dnp3interface_globalqsignal : public QObject { Q_OBJECT public: static dnp3interface_globalqsignal* Instance(); Q_SIGNALS: void onReadHandlerBinaryInput(const CDnpHeaderInfo info, QVector<CDnpBinaryInput>vec); private: static QScopedPointer<dnp3interface_globalqsignal> self; };并在接口继承类中触发该信号：class QReadHandler :public QObject, public dnp3::ReadHandler { Q_OBJECT public: void handle_binary_input(const dnp3::HeaderInfo& info, dnp3::BinaryInputIterator& it) override { QVector<CDnpBinaryInput>vec; while (it.next()) { const auto value = it.get(); vec.append(value); } emit GDNPSIGNAL->onReadHandlerBinaryInput(info, vec); } }同样，在上述代码中，解决了另外一个问题：在Qt信号槽机制中，传递的复杂参数（结构体或类）需具备默认构造函数。但是stepfunc/dnp3将小对象的默认构造函数进行了删除，需要在外部进行一次转换封装（包含默认构造函数），如下述代码所示：struct CDnpBinaryInput { /// @brief Point index uint16_t index; /// @brief Point value bool value; /// @brief Point flags dnp3::Flags flags; /// @brief Point timestam…

WorkEasierTooler 系列（五）读mdb（Access数据库文件）数据
type: item
date: 2022-05-11T21:56:04+08:00
description: “明知道屎难吃的情况下，还要去用手捏一下，凑到鼻子上闻一下，再嚼碎了咽下去”能够相当贴切的形容MFC+ACCESS的组合。一.方案调用COM组件：msado15.dllODBC方式CDatabase CLASSCDaoDataBase CLASS[deprecated]二.选择吃"屎"的方式因为需要随时切换mdb文件，将数据导出来就完事，不适合采用ODBC的方式。反复在x86和x64下调用msado15.dllCOM组件，都没能成功。... ...在艰难的逐个尝试后，采用了CDaoDataBase打开mdb文件，使用CDaoRecordset进行数据读取（查询）的方式，进行mdb数据读操作。缺点：CDaoDataBase、CDaoRecordset，都被标注为deprecated。不支持x64架构。读取方式麻烦，不支持orm方式读写。三.mdb文件打开与关闭添加头文件引用#include <afxdao.h>添加变量申明CDaoDatabase* mdb = nullptrmdb初始化//mdb初始化 if(mdb == nullptr) mdb = new CDaoDatabase;关闭mdb//如果mdb文件处于打开状态，关闭 if (mdb->IsOpen()) mdb->Close();打开mdb//打开数据库 mdb->Open(fileName);判断是否打开//判断是否成功打开 if (!mdb->IsOpen()) { return; }四.数据读取CDaoRecordset初始化//初始化dbset if(dbset == nullptr) dbset = new CDaoRecordset(mdb);使用SQL查询数据表//查询Device子表 dbset->Open(AFX_DAO_USE_DEFAULT_TYPE, _T("SELECT * FROM Device"), NULL);数据读取//数据库字段操作变体类型 COleVariant var; // 字段类型 var.ChangeType(VT_BSTR, NULL); // 此处需要使用VT_BSTR类型，否则会出错，还不清楚原因 CString strName; //value值 //循环读取查询set中数据项 while (!dbset->IsEOF()) //是否到达查询末尾 { //获取DevName字段值 var = dbset->GetFieldValue(_T("DevName")); //char[]类型，需要使用COleVariant.pbstrVal获取 strName = (LPCSTR)var.pbstrVal; m_cmb_device.AddString(strName); //转到下一个查询对象 dbset->MoveNext(); } //关闭查询 dbset->Close();

MCCB调试工具
type: item
date: 2022-05-08T22:05:00+08:00
description: 之前因个人爱好，开发了个MCCB的调试工具，现在用不到了，不忍放硬盘吃灰，就拿出来分享一下。一.整体结构采用Qt开发。基于RS485串口总线通信。采用占有式，占用485总线。二.基本定义1.基础信息定义#pragma once #include <stdint.h> #include <type_traits> #include <QString> #define Mccb_XD_Name "Mccb_XD" #define Mccb_Normal_Name "Mccb_Normal" #define Mccb_Ptlk_Name "Mccb_ElPro" #define Packet_Max_Length 300 #define CMD_READ_DATA 0x11 #define CMD_READ_DATA_NEXT 0x12 #define CMD_READ_COMM_ADDR 0x13 #define CMD_WRITE_DATA 0x14 #define CMD_WRITE_COMM_ADDR 0x15 #define CMD_READ_ALARM 0x19 #define CMD_CTRL 0x1C #define Event_Record_Max 1 namespace Mccb { template <typename Enumeration> auto EnumtoValue(Enumeration const value) -> typename std::underlying_type<Enumeration>::type { return static_cast<typename std::underlying_type<Enumeration>::type>(value); } //返回值 enum class retack :int32_t { PacketNotOver = 0, //帧结构 packetErr, PacketSumCheckErr, packetOk, OkNoNext, //帧内容 OkNeedNext, UpdatePacketErr, RemoteCtrlErr, CommSpeedChangeErr, PasswordErr, NoneRequestData, illegalData, }; //A.2.1 当前变量 enum class DI : uint32_t { /*电压*/ Ua = 0x02010100, Ub = 0x02010200, Uc = 0x02010300, U = 0x0201FF00, /*电流*/ Ia = 0x02020100, Ib = 0x02020200, Ic = 0x02020300, I = 0x0202FF00, /*当前剩余电流*/ IresidualNowMaxPhase = 0x02900000, //当前剩余电流最大相 IresidualNow = 0x02900100, //当前剩余电流值 Iresidual = 0x0290FF00, /*当前额定剩余电流定值*/ Iei_residual = 0x02910100, Iet_residual = 0x02910200, Ie_residual = 0x0291FF00, /*电流*/ IwDirect = 0x02920100, Iwa = 0x02920200, Iwb = 0x02920300, Iwc = 0x02920400, Iw = 0x0292FF00, /*电能*/ Et = 0x00010000, //当前正向有功总电能 EtSub = 0x00020000, Ea = 0x00150000, ESuba = 0x00160000, Eb = 0x00290000, ESubb = 0x002A0000, Ec = 0x003D0000, ESubc = 0x003E0000, /*有功功率*/ Pt = 0x02030000, Pa = 0x02030100, Pb = 0x02030200, Pc = 0x02030300, P = 0x0203FF00, /*无功功率*/ Qt = 0x02040000, Qa = 0x02040100, Qb = 0x02040200, Qc = 0x02040300, Q = 0x0204FF00, /*功率因数*/ Pft = 0x02060000, Pfa = 0x02060100, Pfb = 0x02060200, Pfc = 0x02060300, Pf = 0x0206FF00, /*谐波*/ HVa_1 = 0x020A0101, HVb_1 = 0x020A0201, HVc_1 = …

WorkEasierTooler 系列（四）xml文件解析和excel文件生成
type: item
date: 2022-05-04T22:40:00+08:00
description: 一.第三方库安装及头文件引用vcpkg install tingxml2vcpkg install xlnt#include <tinyxml2.h> #include <xlnt/xlnt.hpp>二.xml文件解析try { //获取文件名 CString fileName; GetDlgItem(IDC_EDIT_FileFullPath)->GetWindowText(fileName); if (fileName.GetLength() <= 0) { MessageBox("请选择文件后再试！", "提示", MB_OK | MB_ICONWARNING); return; } tinyxml2::XMLDocument m_XmlDoc; m_XmlDoc.LoadFile(fileName); //获取第一个元素 XMLElement* xmlElement = m_XmlDoc.FirstChildElement("Grids")->FirstChildElement("Main"); int iCount = 0; //CString txt = "MainName\tGridName\tgridChildName\tbank\tid\tlen\ttype\top\tdefault\tname\tdescription"; while (xmlElement) { //解析节点属性 auto MainName = xmlElement->Attribute("name"); //获取第一个子元素 auto xmlElement_1 = xmlElement->FirstChildElement(); while (xmlElement_1) { //解析节点属性 auto GridName = xmlElement_1->Attribute("name"); //获取第一个子元素 auto xmlNodeRow = xmlElement_1->FirstChildElement(); while (xmlNodeRow) { //判断节点是否包含属性属性 if (xmlNodeRow->FindAttribute("bank")) { //解析节点属性 auto bank = xmlNodeRow->Attribute("bank"); auto id = xmlNodeRow->Attribute("id"); auto len = xmlNodeRow->Attribute("len"); auto type = xmlNodeRow->Attribute("type"); auto op = xmlNodeRow->Attribute("op"); auto defaul = xmlNodeRow->Attribute("default"); auto name = xmlNodeRow->Attribute("name"); auto description = xmlNodeRow->Attribute("description"); iCount++; } else { //获取第一个子元素 auto gridChildXmlEle = xmlNodeRow->FirstChildElement(); if (gridChildXmlEle) { auto gridChildName = xmlNodeRow->Attribute("name"); while (gridChildXmlEle) { if (gridChildXmlEle->FindAttribute("Item")) continue; //解析节点属性 auto bank = gridChildXmlEle->Attribute("bank"); auto id = gridChildXmlEle->Attribute("id"); auto len = gridChildXmlEle->Attribute("len"); auto type = gridChildXmlEle->Attribute("type"); auto op = gridChildXmlEle->Attribute("op"); auto defaul = gridChildXmlEle->Attribute("default"); auto name = gridChildXmlEle->Attribute("name"); auto description = gridChildXmlEle->Attribute("description"); iCount++; //迭代 gridChildXmlEle = gridChildXmlEle->N…

WuuTool（二）字符编码转换
type: item
date: 2022-04-24T22:01:01+08:00
description: 一.事件监听对TextBox的KeyUp事件进行监听处理。 <i:Interaction.Triggers > <i:EventTrigger EventName="KeyUp" > <prism:InvokeCommandAction Command="{Binding TextChanged}" CommandParameter="5" /> </i:EventTrigger> </i:Interaction.Triggers>最初使用的 TextChangedEvent，结果发现赋值会导致反复触发该事件，因此改用了KeyUpEvent。二.编码转换1.GB2312编码支持使用nuget安装system.text.encoding.codepages，在使用GB2312编码之前，增加如下代码：Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);编码转换代码如下：private string _Text; private string _ANSI; private string _GBK; private string _Unicode; private string _Utf_8; private string _Utf_7; //文本 public string Text { get { return _Text; } set { SetProperty(ref _Text, value); } } //ASCII 编码 public string ANSI { get { return _ANSI; } set { SetProperty(ref _ANSI, value); } } //GB2312 编码 public string GBK { get { return _GBK; } set { SetProperty(ref _GBK, value); } } //Unicode 编码 public string Unicode { get { return _Unicode; } set { SetProperty(ref _Unicode, value); } } //Utf-8 编码 public string Utf_8 { get { return _Utf_8; } set { SetProperty(ref _Utf_8, value); } } //Utf-7 编码 public string Utf_7 { get { return _Utf_7; } set { SetProperty(ref _Utf_7, value); } } private DelegateCommand<string> _textChanged; public DelegateCommand<string> TextChanged => _textChanged ?? (_textChanged = new DelegateCommand<string>(_TextChanged)); void _TextChanged(string str) { try { switch (str) { case "0": //输入文本 if (Text?.Length > 0) { ANSI = BitConverter.ToString(Encoding.ASCII.GetBytes(Text)).Replace("-", " "); GBK = BitConverter.ToString(Encoding.GetEncoding("GB2312").GetBytes(Text)).Replace("-", " "); Unicode = BitConverter.ToString(Encoding.Unicode.GetBytes(Text)).Replace("-", " "); Utf_8 = BitConverter.ToString(Encoding.UTF8.GetBytes(Text)).Replace("-", " "); Utf_7 = BitConverter.ToString(Encoding.UTF7.GetBytes(Text)).Replace("-", " "); } else { ANSI = ""; GBK = ""; Unicode = ""; Utf_8 = ""; Utf_7 = ""; } break; case "1": // 输入ASCII编码 if(ANSI?.Length>0) { Text = Encoding.ASCII.GetString(ToBytesFromHexString(ANSI.Repla…

WuuTool（一）重拾旧梦
type: item
date: 2022-04-23T23:36:00+08:00
description: 去年折腾了一段时间的WuuTool开发，中间因为种种原因，鸽了。思来想去，最终还是把它给捡起来，然后进行完善。一.解决问题1.修复了字体模糊的bug之前文字在bord内，且bord设置了effect，导致文字胡的简直没法看。通过查阅，发现应该采用如下方式：<Grid> <Border Margin="2" BorderThickness="1" Width="180" Height="50" Background="White" CornerRadius="5"> <Border.Effect> <DropShadowEffect Color="#Dcdcdc" ShadowDepth="2" BlurRadius="10" Opacity="0.8" Direction="270" RenderingBias="Performance"/> </Border.Effect> </Border> <StackPanel Margin="5" Orientation="Horizontal" UseLayoutRounding="True"> <Image Margin="5 0" RenderOptions.BitmapScalingMode="HighQuality" Width="30" Stretch="Uniform" Source="{Binding Ico}"/> <TextBlock Margin="10 0" VerticalAlignment="Center" Text="{Binding FileName}"/> </StackPanel> </Grid>而不是：<Border Margin="2" BorderThickness="1" Width="180" Height="50" Background="White" CornerRadius="5"> <Border.Effect> <DropShadowEffect Color="#Dcdcdc" ShadowDepth="2" BlurRadius="10" Opacity="0.8" Direction="270" RenderingBias="Performance"/> </Border.Effect> <StackPanel Margin="5" Orientation="Horizontal" UseLayoutRounding="True"> <Image Margin="5 0" RenderOptions.BitmapScalingMode="HighQuality" Width="30" Stretch="Uniform" Source="{Binding Ico}"/> <TextBlock Margin="10 0" VerticalAlignment="Center" Text="{Binding FileName}"/> </StackPanel> </Border>2.解决了ListBoxItem双击事件触发问题<ListBox.ItemTemplate> <DataTemplate > <Grid> <Grid.InputBindings> <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding RunAsNormalCommand}"/> </Grid.InputBindings> </Grid> </DataTemplate> </ListBox.ItemTemplate>3.去除了MaterialDesignTheme依赖由于MaterialDesignTheme主题与自定义样式冲突，导致两者无法并存。因此对MaterialDesignTheme进行了剥离去除。4.初步完成TextBox美化<Grid Margin="5"> <Rectangle HorizontalAlignment="Stretch" Fill="#1ab394" Margin="0 4" Height="1" VerticalAlignment="Bottom"/> <TextBox Height="26" Margin="0 0 5 -10" x:Name="floatNum" MinWidth="150" MaxWidth="150" BorderThickness="0" Background="Transparent" HorizontalAlignment="Center" Text="{Binding FloatStr1}" Cursor="IBeam"> </TextBox> </Grid>二.目前进展情况1.快速启动功能基本完成2.实用工具：浮点转换完成三…

WorkEasierTooler 系列（三）101/104报文解析（续）
type: item
date: 2022-04-10T23:00:00+08:00
description: 基于经常需要分析一大段报文，因此对101/104报文解析做了个修正，支持多条依次解析。同时加上了101/104报文的短帧解析。一.多帧报文依次解析/// /// 规约解析 /// void CProtocolToolDlg::OnBnClickedButton1() { try { /*获取字节长度配置信息*/ uint8_t linkAddrLen = 2, asduLen = 2, cotLen = 2, infoAddrLen = 2, balancedMode = 0; linkAddrLen = ((CComboBox*)GetDlgItem(IDC_CMB_LINKADDRLEN))->GetCurSel() + 1; asduLen = ((CComboBox*)GetDlgItem(IDC_CMB_ASDULEN))->GetCurSel() + 1; cotLen = ((CComboBox*)GetDlgItem(IDC_CMB_COTLEN))->GetCurSel() + 1; infoAddrLen = ((CComboBox*)GetDlgItem(IDC_CMB_INFOADDRLEN))->GetCurSel() + 1; balancedMode = ((CComboBox*)GetDlgItem(IDC_CMB_BALANCEMODE))->GetCurSel(); CString text; GetDlgItemText(IDC_EDIT_Protocol, text); if (text.IsEmpty()) { MessageBox("请输入十六进制文本字符串后再试！", "提示"); return; } /*十六进制报文文本处理*/ text.Replace(" ", ""); text.Replace("0x", ""); text.Replace(",", ""); text.Replace("\n", ""); text.Replace("\r", ""); text.Replace("\t", ""); text.Replace(";", ""); if (text.Find("x") != -1) { MessageBox("包含非法字符'x'！", "提示"); return; } /*十六进制报文文本转 uint8_t[]*/ uint8_t* tmp = new uint8_t[text.GetLength()]; memset(tmp, 0, text.GetLength()); ByteHandler::Str2Hex(tmp, text.GetBuffer(), text.GetLength() / 2); text.ReleaseBuffer(); uint32_t startIndex = 0; //当前解析起始位置 uint8_t packetLen = 0; //当前报文长度 CAtlString text11; //解析文本 uint8_t* ptr_buffer = tmp; while (startIndex< text.GetLength() / 2) { /*报文解析类初始化*/ ProtocolCheck pCheck(ptr_buffer, text.GetLength() / 2-startIndex, linkAddrLen, asduLen, cotLen, infoAddrLen, balancedMode); /*判断是否为101/104报文*/ auto pType = pCheck.IsDL10xPacket(packetLen); if (pType != 1 && pType != 2) { /*未找到一条完整报文*/ SetDlgItemText(IDC_EDIT_Protocol_Result, "非101/104格式报文"); break; } /*控制字、传送原因、公告地址和类型标志解析*/ char str_ctrl[64] = { 0 }, str_cot[64] = { 0 }, str_asdu[64] = { 0 }, str_ti[64] = { 0 }; auto ctrl = pCheck.GetCtrlWord(str_ctrl); auto cot = pCheck.GetCotWord(str_cot); auto asdu = pCheck.GetAsduWord(); auto ti = pCheck.GetTi(str_ti); /*单帧报文提取，并转换为 文本*/ char tmpStr[255 * 3 + 1] = { 0 }; ByteHandler::Hex2Str(tmpStr…

visual studio 2019/2022 与 vcpkg 的那些火花
type: item
date: 2022-04-05T20:41:00+08:00
description: 这些年，走马观花，易语言、java、c/c++、c#、python都有所耳闻，也浅尝过MFC、Qt、WinForm、Wpf还有MAUI之类的UI框架。但是，我最爱的，还是c++和MFC。只是，在Windows上，一直没有比较完美的包管理工具，导致调轮子的方式很繁琐：源码包含或者手动编译，再静态/动态链接。听闻vcpkg有段时间了，一直没去细究到底能做啥，能给自己的业余爱好带来啥便捷。于是蹭着假期，花了1天时间折腾了一波。一.vcpkg简介vcpkg is a free C/C++ package manager for acquiring and managing libraries. Choose from over 1500 open source libraries to download and build in a single step or add your own private libraries to simplify your build process. Maintained by the Microsoft C++ team and open source contributors.二.vcpkg安装1.安装git、vs2019/vs2022、cmake2.git clone https://github.com/Microsoft/vcpkg.git3. 在powershell中，cd vcpkg .bootstrap-vcpkg.bat三.vcpkg使用具体可参考 Visual Studio开源库集成器Vcpkg全教程--利用Vcpkg轻松集成开源第三方库_Achilles的博客-CSDN博客_vcpkg四.注意事项若需要全局包含：需要在visual studio中使用通过vcpkg安装的第三方包。需要在vcpkg安装第三方包之前，执行.\vcpkg integrate install，否则会出现怎么都无法引用已安装第三方包问题！！！

WorkEasierTooler 系列（二）101/104报文解析
type: item
date: 2022-04-02T21:40:00+08:00
description: 101/104报文解析完结最终还是采用了预设定链路地址长度（101规约报文）、公共地址长度、传送原因长度和信息体地址长度的方式。模糊匹配的方式不是很靠谱（主要还是水平有限）。一.完整类头文件定义#pragma once #include <string> #include <vector> //信息体对象 struct InfoData { uint32_t Address; //地址 std::string Data; //数据 std::string TimeStamp; //时间戳（部分报文带时间戳，比如带时间戳的单/双点遥信，对时。。。） }; //CP56Time2a结构体 typedef union { uint8_t byte[7]; struct { WORD millisecond; uint8_t minute : 6; uint8_t RES1 : 1; uint8_t IV : 1; uint8_t hour : 5; uint8_t RES2 : 2; uint8_t SU : 1; uint8_t dayofmonth : 5; uint8_t dayofweek : 3; uint8_t month : 4; uint8_t RES3 : 4; uint8_t year : 7; uint8_t RES4 : 1; }; } unionCP56Time; class ProtocolCheck { public: /// /// 初始化 /// /// byte[] /// byte[]长度 /// 链路地址长度 /// 公共地址长度 /// 传送原因长度 /// 信息体地址长度 ProtocolCheck(uint8_t* buf, uint16_t len, uint8_t linkaddrlen, uint8_t asdulen, uint8_t cotlen, uint8_t infoaddrlen) { uBuffer = buf; uLen = len; uIndex = 0; uLinkAddrLen = linkaddrlen; uCotLen = cotlen; uAsduLen = asdulen; uInfoAddrLen = infoaddrlen; uType = 0; uPacketLen = 0; } ~ProtocolCheck(); /// /// 判断是否为101/104报文，忽略短帧 /// /// <returns>1:101,2:104,-1:校验和错,-2:非101/104报文</returns> int IsDL10xPacket(uint8_t& len); /// /// 获取控制字 /// /// 控制字解析，为空时无 /// <returns></returns> uint32_t GetCtrlWord(char* descript=nullptr); /// /// 获取链路地址 /// /// 控制字解析，为空时无 /// <returns></returns> uint16_t GetLinkAddr(char* descript = nullptr); /// /// 获取传送原因 /// /// 传送原因解析，为空时无 /// <returns></returns> uint16_t GetCotWord(char* descript = nullptr); /// /// 获取公共地址 /// /// 公共地址解析，为空时无 /// <returns></returns> uint16_t GetAsduWord(char* descript = nullptr); /// /// 获取类型标志 /// /// 类型标志解析，为空时无 /// <returns></returns> uint8_t GetTi(char* descript = nullptr); /// /// 获取信息体数目 /// /// 信息体对象 /// <returns></returns> std::vector<InfoData> GetInfoDataNum(); private: //待解析的报文数据 uint8_t* uBuffer; //待解析的报文数据字节长度 uint16_t uLen; //有效数据帧起始地址 uint16_t uIndex; //链路地址长度 uint8_t uLinkAddrLen; //传送原因长度 uint8_t uCotLen; //公共地址长度 uint8_t uAsduLen; //信息体地址长度 uint8_t uInfoAddrLen; //报文类型，1:101报文，2:104报文 uint8_t uT…