多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

modbus驱动编写

modbus驱动编写 即使对方给的协议modbus 青鸟我还是不知道怎么写因为往往上位机如何使用这些接口数据也不是那么简单就3个接口但是使用起来却也有些麻烦那么怎么编写这个驱动呢现在有了AI编程确实厉害我不懂.net它能帮我按照我想法编出来但作为开发工程师还是要看懂它到底写了些什么驱动驱动注解c#中规定如果一个类继承自 System.Attribute那么在使用时可以省略类名的 Attribute 后缀。下面代码相当于定义了Driver的一个注解// AttributeTargets.Class类似Target(ElementType.TYPE)标识只能用在类上// Inherited false子类不继承这个注解Java 里还要加 Retention(RetentionPolicy.RUNTIME)否则反射读不到。C# 默认就是运行时可见不用额外声明。[AttributeUsage(AttributeTargets.Class,Inheritedfalse)]publicsealedclassDriverAttribute(stringname):Attribute{publicstringName{get;}name;}// sealed 标识不能被集成跟java的final一样[Driver(jbf293k)]publicsealedclassJbf293kDriver:IDeviceDriver{// C# 可空引用类型。null! 意思是“我知道这里是 null但编译器你别警告我我保证后面会赋值”。Java 没这个概念。privateDeviceContext_ctxnull!;// 问号表示这个字段可以为 null。Java 里所有对象都可为 nullC# 默认不可为 null需要显式加 ?privateIFrameTransport?_transport;// C# 9 的目标类型 new等价于 new SemaphoreSlim(1, 1)。Java 10 也有 var但 C# 这个更激进连类型名都省了。privatereadonlySemaphoreSlim_ioLocknew(1,1);privatebool_isTcp;privatebyte_unit;privatestring_machineTypealarm;// ushort无符号 16 位整数0~65535Java 没有只能用 int 代替。privateushort_tid;// 是表达式体成员等价于 { return ...; }。publicstringDriverNamejbf293k;// { get; } 是只读属性只能读不能写类似 Java 的 final 字段 getter。// SupportsPolling: true 是命名参数Java 没有只能按构造函数参数顺序传。但python有publicDriverCapabilitiesCapabilities{get;}new(SupportsPolling:true,SupportsReport:false,SupportsCommands:false,DefaultPollIntervalMs:2000);// C# 的 event 是委托类型的事件类似 Java 的监听器模式。// event事件本质是对委托的封装。对外只能 add、-remove不能直接赋值。 // ?可空这个事件可以是 null没有订阅者时就是 null// add当外部代码 obj.OnTelemetry Handler 的时候执行 add 里面的代码// remove当外部代码 obj.OnTelemetry - Handler 的时候执行 remove 里面的代码// 纯轮询型上报事件不支持publiceventFuncTelemetry,Task?OnTelemetry{add{}remove{}}/// summary/// 这是驱动启动时调用的方法相当于 Java 里的 PostConstruct 或 init()。/// Task 是 C# 的异步返回类型等价于 Java 的 CompletableFuture。Task无泛型相当于 CompletableFutureVoid。/// CancellationToken 是 C# 的取消令牌等价于 Java 的 Thread.interrupt() 或 Reactor 的 Subscription。只是用途上有部分重叠底层模型完全不一样。/// /summarypublicTaskStartAsync(DeviceContextcontext,CancellationTokenct){// 保存上下文_ctxcontext;varconncontext.Connection;// 从站地址如果配置为0默认用1_unit(byte)(conn.UnitId0?1:conn.UnitId);// 读取配置// ?? 是空合并运算符等价于 Java 的 ! null ? :。// Dictionary类java中的Map键值对容器也是非线程安全varoptsconn.Options??newDictionarystring,string();// TryGetValue(machineType, out var mt) 是 C# 的输出参数Java 没有// 如果字典里有 machineType 这个键返回 true并且把**键对应的 value 塞给 mt**如果**没有这个键**返回falsemtnull。 _machineTypeopts.TryGetValue(machineType,outvarmt)mt.Length0?mt:alarm;// 位表字典即 machineType 合法值清单启动即校验非法配置不能静默降级_Jbf293kProtocol.BitTableFor(7,_machineType);// 线格式options.protocol 显式指定tcp/rtu缺省按连接类型推导serial → RTU其余 → TCPvarprotocolopts.TryGetValue(protocol,outvarp)p.Length0?p.ToLowerInvariant():(conn.ConnectionType.Equals(serial,StringComparison.OrdinalIgnoreCase)?rtu:tcp);_isTcpprotocolswitch{tcptrue,rtufalse,_thrownewNotSupportedException($options.protocol {protocol} 非法支持 tcp / rtu),};// 非法组合启动即报错能力声明不能静默降级varctLowerconn.ConnectionType.ToLowerInvariant();if(_isTcpctLowerisnot(tcp))thrownewNotSupportedException($Modbus TCP 线格式要求 connectionTypetcpZLAN 透传当前 {conn.ConnectionType});if(!_isTcpctLowerisnot(serialortcp))thrownewNotSupportedException($Modbus RTU 线格式要求 connectionTypeserial485 直连或 tcpRTU over TCP当前 {conn.ConnectionType});// 点表全量预校验编址合法 位名存在于机型位表 属性名不重复// StringComparer.Ordinal序号比较二进制逐 char 编码值对比区分大小写速度最快文化无关// StringComparer.OrdinalIgnoreCase 序号比较忽略大小写二进制对比大小写不敏感工业场景常用varseennewHashSetstring(StringComparer.Ordinal);foreach(varpointincontext.Points){// 元组解构 var (loop, part, bit) ...C# 特有var(loop,part,bit)Jbf293kProtocol.ParseRawPoint(point.RawPoint);// _ 又是“不关心返回值”。if(bitisnotnull)_Jbf293kProtocol.FindBit(loop,_machineType,bit);// HashSet.Add 返回 bool如果元素已存在返回 false!false true触发异常。等价于 Java 的 if (!seen.add(...)) throw ...。if(!seen.Add(point.Property))// $... 是字符串插值等价于 Java 15 的 ... variable 或 String.formatthrownewInvalidDataException($点表属性名重复{point.Property});}// 表示“同步完成的 Task”等价于 Java 的 CompletableFuture.completedFuture(null)returnTask.CompletedTask;}// 核心轮询逻辑// async 表示方法内部有 await等价于 Java 的 CompletableFuture 链式调用。publicasyncTaskIReadOnlyListTelemetryPollAsync(CancellationTokenct){// 加锁await_ioLock.WaitAsync(ct);try{// ?? 是空合并赋值如果左边是 null就赋值否则不变。_transport??FrameTransport.Create(_ctx.Connection);if(!_transport.IsConnected)await_transport.ConnectAsync(ct);// 断线由 DriverRuntime 退避重试// 解析点表// Select 等价于 Java 的 stream().map()// (Pt: pt, Loop: 0, ...) 是命名元组C# 特有// 按回路 × 页分组同一回路的点只可能落在两页1~100 / 101~200每页整查 100 个寄存器varparsed_ctx.Points.Select(pt(Pt:pt,Loop:0,Part:0,Bit:(string?)null)).ToList();for(inti0;iparsed.Count;i){var(loop,part,bit)Jbf293kProtocol.ParseRawPoint(parsed[i].Pt.RawPoint);parsed[i](parsed[i].Pt,loop,part,bit);}// 按回路分组轮询varpropsnewListTelemetryProperty(_ctx.Points.Count);// GroupBy(x x.Loop) Java 的 Collectors.groupingBy。// OrderBy(g g.Key) Java 的 sorted(Comparator.comparing(...))。// grp.Key 分组键回路号// BitTableFor(loop, _machineType)取该回路的位表。//foreach(vargrpinparsed.GroupBy(xx.Loop).OrderBy(gg.Key)){intloopgrp.Key;vartableJbf293kProtocol.BitTableFor(loop,_machineType);// PageStarts [1, 101]两页。foreach(varpageStartinJbf293kProtocol.PageStarts){// grp.Where(...) Java 的 filter。// item.Part pageStart item.Part pageStart 100判断这个点属于哪一页varpagePointsgrp.Where(xx.PartpageStartx.PartpageStartJbf293kProtocol.PageQuantity).ToList();if(pagePoints.Count0)continue;ushortstartJbf293kProtocol.PageAddress(loop,pageStart);ushorttid_tid;// 构造请求帧varrequestJbf293kProtocol.BuildReadRequest(_isTcp,_unit,start,tid);await_transport.WriteAsync(request,ct);// 读应答返回 200 字节数据。vardataawaitJbf293kProtocol.ReadResponseAsync(_transport,_isTcp,_unit,tid,ct);foreach(variteminpagePoints){// 算出这个点在 200 字节数据里的位置intoffsetitem.Part-pageStart;// 部位号在页内偏移 部位号 - 页起始// 取出对应的 2 字节寄存器值。ushortregJbf293kProtocol.RegisterAt(data,offset);props.Add(item.Bitisnull?newTelemetryProperty(item.Pt.Property,Jbf293kProtocol.Summary(reg,table)):newTelemetryProperty(item.Pt.Property,Jbf293kProtocol.ReadBit(reg,Jbf293kProtocol.FindBit(loop,_machineType,item.Bit))));}}}// 构造一个遥测属性return[newTelemetry(_ctx.DeviceId,DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),props,_ctx.ProductKey)];}// 释放锁finally{_ioLock.Release();}}// 表达式体等价于 Java 的 { return ...; }。// 这个方法直接返回“不支持命令”因为 JBF293K 是只读设备。publicTaskCommandResultInvokeAsync(DeviceCommandcommand,CancellationTokenct)Task.FromResult(newCommandResult(false,$驱动{DriverName}为只读火警输出卡协议仅 0x03 读保持寄存器不支持命令 {command.Name}));// ValueTask 是 Task 的轻量版用于可能同步完成的异步方法。Java 没有直接对应用 CompletableFuture 就行。publicasyncValueTaskDisposeAsync(){// _transport is { } t 是模式匹配如果 _transport 不是 null就赋给 t。等价于 Java 的 if (_transport ! null) { var t _transport; ... }。if(_transportis{}t)awaitt.DisposeAsync();_ioLock.Dispose();}}
返回列表