RO39 – 在一个事务中实现多个ClientDataSets 更新

本文介绍了一种从客户端控制的事务迁移至服务端控制事务的方法。通过在服务端创建处理ClientDataSet.Delta集合的方法,并在单独事务中更新数据库,确保了数据的一致性和事务的原子性。该方法适用于非嵌套数据集的场景。

从两层迁移到三层第一个要面临的窍门就是摆脱在客户端控制事务.客户端不应该开始和提交事务.事实上客户端应该不知道任何事物.所有的事务逻辑应该放在服务端.
本文向你展示如何在RemObjects DataSnap服务中创建一个方法,用以传递一个ClientDataSet. Delta集合并在单独的事务中向数据库中更新,以便于在更新时发生异常时回滚事务.
这个方法只适用于你使用非嵌套(主从关系)的ClientDataSets 的情况,因为DataSnap默认情况下已经在单独事务中使用嵌套(主从关系)数据集.
你现在需要一个RO DataSnap server,由于我不打算介绍如何创建这种服务,所以我们使用一个已经存在的服务.
首先,你需要在服务端RODL中创建一下数据类型.使用RO Service Builder,创建一个包含ProviderName (string) 和 Delta (binary)成员的DeltaToApply结构体:

然后创建DeltaToApply类型的数组:


接下来,在服务端创建接收这个数组的函数.我将这个函数添加到包含所有Provider的同一个服务中,因为我们要使用这个Provider更新数据:

方法实现如下: 
procedure TNewService.ApplyUpdates(var ADeltaArray: DeltaArray);
var
 I: Integer;
 Provider: TDataSetProvider;
 ErrorCount: Integer;
begin
 // Put your code to start transaction
 try
    for I := 0 to ADeltaArray.Count - 1 do
    begin
      Provider := FindProvider(ADeltaArray[I].ProviderName);
      if not Assigned(Provider) then
        raise Exception.Create('Provider not found: ' + ADeltaArray[I].ProviderName);
 
      Provider.ApplyUpdates(VariantFromBinary(ADeltaArray[I].Delta), 0, ErrorCount);
      if ErrorCount > 0 then
        // Put your code to handle errors
        raise Exception.Create('Errors during applyupdates: ' + Provider.Name);
    end;
    // Put your code to commit the transaction
 except
    // Put your code to rollback the transaction
    raise;
 end;
end;
我已经创建了一个帮助函数去根据名字获取一个Provider:
function TNewService.FindProvider(ProviderName: string): TDataSetProvider;
var
 Component: TObject;
begin
 Component := FindComponent(ProviderName);
 if Component is TDataSetProvider then
    Result := Component as TDataSetProvider
 else
    Result := nil;
end;
服务端完成.在客户端,你需要创建一个方法将所有的ClientDataSet.Delta保存在数组并将其发送到服务端:
procedure TClientForm.ApplyUpdates(ClientDataSets: array of TClientDataSet);
var
 Deltas: DeltaArray;
 Delta: DeltaToApply;
 I: Integer;
begin
 Deltas := DeltaArray.Create;
 try
    for I := Low(ClientDataSets) to High(ClientDataSets) do
    begin
      if ClientDataSets[I].ChangeCount = 0 then
        Continue;
 
      Delta := Deltas.Add;
      Delta.ProviderName := ClientDataSets[I].ProviderName;
      Delta.Delta := BinaryFromVariant(ClientDataSets[I].Delta);
    end;
    CoNewService.Create(ROMessage, ROChannel).ApplyUpdates(Deltas);
 finally
    Deltas.Free;
 end;
end;
VariantFromBinary 和 BinaryFromVariant 方法在uROBinaryHelpers单元内.
最后,你只需要在客户端调用这个方法传递所有的你需要在同一个事务中更新的ClientDataSet:
ApplyUpdates([ClientDataSet1, ClientDataSet2, ClientDataSet3]);
好了!我希望这能对你有帮助.如果你发现代码存在问题或有可以改进的地方请尽快通知我以便于修正.

<think>我们正在讨论三菱PLC RO4ENCPU同时读取多个Modbus设备数据的方法。根据之前的回答,我们知道RO4ENCPU有两个串行通信端口(CH1和CH2),每个端口都可以作为Modbus RTU主站。因此,我们可以通过以下两种方式实现同时读取多个设备: 1. 利用两个独立的通信端口(CH1和CH2)分别连接两个Modbus总线,每个总线上挂接不同的从站设备。 2. 在同一个通信端口(如CH1)上连接多个从站设备,通过分时轮询的方式读取。 由于RO4ENCPU的每个串行通信端口只能同时处理一个Modbus事务(半双工),所以同一端口上的多个设备需要通过程序轮询读取。 下面我们详细说明这两种方法: ### 方法一:双端口独立通信(硬件级并行) **适用场景**:需要高速采集或设备数量较多时 **硬件连接**: - CH1端口连接一组Modbus从站(设备1~n) - CH2端口连接另一组Modbus从站(设备n+1~m) **注意**:每个端口的总线拓扑需符合RS485规范(终端电阻、屏蔽双绞线等) **程序实现**: - 两个端口可同时执行`G.ADPRW`指令,因为它们是独立的硬件通道。 - 示例程序结构: ```ladder // 端口1读取(例如站号1的流量计) |--[G.ADPRW H1 K1 H4 K40001 K2 D100]---| // CH1读取站号1 // 端口2读取(例如站号2的流量计) |--[G.ADPRW H2 K2 H4 K40001 K2 D200]---| // CH2读取站号2 ``` - **关键点**:两个指令可以同时执行,因为K1和K2分别代表不同的通信端口。 ### 方法二:单端口轮询通信(程序分时) **适用场景**:设备数量少且通信速率要求不高时 **硬件连接**:所有从站(站号1~m)并联在CH1(或CH2)的RS485总线上。 **程序实现**: - 使用轮询机制,依次读取每个站号,并设置合理的延时防止总线冲突。 - 示例程序结构(以3个设备为例): ```ladder |--[M0]--------------------------------| // 启动读取(例如由定时器触发) |--[MOV K1 D0]-------------------------| // 当前站号存储到D0 |--[G.ADPRW D0 K1 H4 K40001 K2 D100]---| // 读取站号D0的设备 |--[INC D0]----------------------------| // 站号+1 |--[CMP K3 D0]-------------------------| // 比较是否超过站号3 |--[<] [MOV K1 D0]---------------------| // 若超过则重置为1 ``` - **关键点**: 1. 每次执行`G.ADPRW`指令后,需等待该指令完成(通过指令完成标志位)再执行下一个。 2. 建议在每次读取之间插入至少50ms延时(使用`TM`定时器),避免总线响应冲突。 ### 错误处理机制 无论哪种方法,都需要考虑通信失败的情况: - 使用`G.ADPRW`指令的错误标志位(例如指令输出点的上升沿检测错误)。 - 检查特殊寄存器: - CH1错误代码:`SD20601` - CH2错误代码:`SD20611` - 典型处理流程: ```ladder |--[G.ADPRW ...]-----------------------| // 执行指令 |--[| ] [M8029]-----------------------| // 指令完成 |--[| ] [M8329+]----------------------| // 指令错误(M8329为指令错误标志,具体地址需查手册) |--[| ] [MOV SD20601 D500]------------| // 存储错误代码 ``` ### 性能优化建议 1. **通信超时设置**:在GX Works2的串口参数中设置超时时间(默认500ms),避免因设备故障导致长时间等待。 2. **异步执行**:对于双端口方案,两个端口的读取完全独立,可同时触发。 3. **分时策略**:单端口轮询时,将重要设备放在前面读取,并设置不同的采集周期(如关键设备每1秒读,次要设备每10秒读)。 ### 注意事项 1. **站号冲突**:确保同一总线上的设备站号唯一。 2. **总线负载**:单总线上设备数量建议不超过32个,且总长度不超过1200米。 3. **终端电阻**:总线两端(首尾设备)需启用120Ω终端电阻。 通过以上方法,RO4ENCPU可以高效地同时读取多个Modbus设备数据。实际应用中,双端口方案能提供更高的通信吞吐量。 §§相关问题§§ 1. 如何设置RO4ENCPU串行端口的通信超时时间? 2. Modbus RTU通信中如何诊断和解决CRC校验错误? 3. 三菱PLC如何通过以太网扩展Modbus通信能力?</think>以下是三菱PLC RO4ENCPU同时读取多个Modbus设备数据的完整解决方案,结合硬件配置和程序实现: ### 一、硬件架构设计 1. **RS485总线拓扑** ```mermaid graph LR RO4ENCPU[PLC RS485端口] -->|总线A/B线| 设备1[流量计1] RO4ENCPU -->|并联连接| 设备2[流量计2] RO4ENCPU -->|终端电阻| 设备3[流量计3] ``` - 使用**单条RS485总线**连接所有设备(最多32节点) - 总线两端需安装**120Ω终端电阻**[^2] - 线缆要求:屏蔽双绞线(长度≤1200米) 2. **设备地址分配** | 设备名称 | 站号 | 寄存器地址 | |----------|------|------------| | UXBRH-1 | 1 | 40001 | | UXBRH-2 | 2 | 40001 | | UXBRH-3 | 3 | 40001 | ### 二、PLC程序实现 #### 方法1:顺序轮询(推荐) ```ladder // 轮询控制程序 |--[M8000]-------------------------------| // 常ON触点 |--[TM0 K50]-----------------------------| // 50ms间隔定时器 |--[PLS M10]-----------------------------| // 生成读取脉冲 // 站号切换控制 |--[M10]---------------------------------| | |--[INC D0]-------------------------| // 站号计数器+1 | |--[CMP K4 D0]----------------------| // 检测是否超过最大站号 | |--[>=] [MOV K1 D0]-------------| // 复位站号 // 执行读取指令 |--[M10]---------------------------------| | |--[G.ADPRW D0 K1 H4 K40001 K2 D100]| // 关键指令 | | ├─ H: 从站地址(D0) | | ├─ K1: CH1端口 | | ├─ H4: 功能码04 | | ├─ K40001: 流量计总量寄存器 | | └─ D100: 数据存储首地址 | |--[M8029]-------------------------------| // 指令完成标志 | |--[BMOV D100 D200Z0 K4]------------| // 数据转存 | | ├─ D200Z0: 变址存储 | | └─ Z0=站号×100 (地址偏移) ``` #### 方法2:并行处理(双端口) ```ladder // 端口CH1读取站号1 |--[G.ADPRW H1 K1 H4 K40001 K2 D100]-----| // 端口CH2读取站号2 |--[G.ADPRW H2 K2 H4 K40001 K2 D200]-----| // 定时触发站号3 |--[TM0 K100]---------------------------| |--[PLS M0]------------------------------| |--[G.ADPRW H3 K1 H4 K40001 K2 D300]-----| ``` ### 三、关键参数配置 1. **串口参数设置**(GX Works2) ```json { "端口": "CH1/CH2", "协议": "Modbus RTU", "波特率": 9600, "数据位": 8, "停止位": 1, "校验": "无", "超时": 500ms } ``` 2. **通信时序优化** | 参数 | 推荐值 | 说明 | |------|--------|------| | 指令间隔 | ≥50ms | 防止总线冲突 | | 响应超时 | 300ms | 设备无响应等待时间 | | 轮询周期 | 1s | 全设备扫描周期 | ### 四、错误处理机制 1. **状态监控寄存器**: - `SD20600`:CH1通信状态 - `SD20610`:CH2通信状态 - `SD20601`:CH1最新错误码 2. **错误处理程序**: ```ladder |--[M8329]-------------------------------| // CH1通信错误标志 | |--[MOV SD20601 D900]---------------| // 存储错误码 | |--[CMP K16#0010 D900]-------------| // CRC错误检测 | |--[=] [CALL P0]---------------| // 调用重试程序 ``` ### 五、注意事项 1. **总线冲突避免**: - 确保同一时刻只有一个设备在发送数据 - 使用`TM`定时器控制指令间隔时间≥50ms[^2] 2. **数据存储策略**: ```mermaid graph TB 读取完成 --> 数据转存 数据转存 -->|按站号分区| D200+D300+D400 数据转存 -->|时间戳记录| D500-D503 ``` 3. **性能极限**: - 单端口最大设备数:32个(理论值) - 实际推荐值:≤15个(保证1秒内完成轮询) > **调试建议**:先用**单设备测试**确保基础通信正常,再逐步增加设备数量[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值