ModbusClientCommon.cin

Introduced loops to automatically split requests that are too large for Modbus

ModbusClient.can
  Modified Modbus events so that the split requests (see above) will be written at the correct position in sys vars

ModbusFunctions.cin
DeviceInformation.cin
  Introduced new file that will handle most device specific things

ModbusStructs.cin
  Introduced new constants with maximum Modbus values

MakeConfig.can
  Increment IP address with swapDWord
  Moved detection stuff to DeviceInformation.cin
This commit is contained in:
Jonny007-MKD 2014-06-17 14:21:45 +00:00
parent 9dc7ffd47d
commit 93dd56469d
10 changed files with 648 additions and 531 deletions

View File

@ -1,4 +1,4 @@
;CANoe Version |4|7|1|35745 MakeConfig
;CANoe Version |4|7|1|52148 MakeConfig
Version: 8.2.40 Build 40
32 PRO
5
@ -13,10 +13,10 @@ VGlobalParameters 2 Begin_Of_Object
20
0
3,100,200,500
1000000 1.000000 1 1000 1 1 0 0 1 1 1 0 0 0 1 0 0 0
1000000 1.000000 0 1000 1 1 0 0 1 1 1 0 0 0 1 0 0 0
1
0
0 1
0 0
ResetSignalsOnMeasurementStart=1
VDatabaseContainerStreamer 3 Begin_Of_Object
5
@ -642,7 +642,7 @@ Begin_Of_Multi_Line_String
Copyright (c) 2001-2006 Actipro Software LLC. All rights reserved.
http://www.ActiproSoftware.com/Products/DotNet/
--><ToolWindowLayout Version="1.0"><LayoutData><HostContainerControl><ToolWindowContainer Dock="Top" Size="1192, 205" SelectedToolWindowGuid="5b8b4004-bff2-40a1-b0bf-fd41707807dc"><ToolWindow Key="{8F3DFCAC-9CCB-45C2-AF10-5DEC039B5956}" Guid="5b8b4004-bff2-40a1-b0bf-fd41707807dc" DockedSize="201, 201" FloatingSize="300, 180" HasOptions="False" ImageIndex="-1" Text="Write" TitleBarText="Write"><AutoHideStateInfo RootDock="Top" /><DockedInsideHostStateInfo RootDock="Top" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></ToolWindowContainer></HostContainerControl><AutoHide Dock="Left" /><AutoHide Dock="Right" /><AutoHide Dock="Top" /><AutoHide Dock="Bottom" /><TabbedDocuments Orientation="Horizontal" /><FloatingContainers /><Hidden /></LayoutData><CustomData /></ToolWindowLayout>
--><ToolWindowLayout Version="1.0"><LayoutData><HostContainerControl><ToolWindowContainer Dock="Top" Size="1192, 267" SelectedToolWindowGuid="5b8b4004-bff2-40a1-b0bf-fd41707807dc"><ToolWindow Key="{8F3DFCAC-9CCB-45C2-AF10-5DEC039B5956}" Guid="5b8b4004-bff2-40a1-b0bf-fd41707807dc" DockedSize="201, 263" FloatingSize="300, 180" HasOptions="False" ImageIndex="-1" Text="Write" TitleBarText="Write"><AutoHideStateInfo RootDock="Top" /><DockedInsideHostStateInfo RootDock="Top" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></ToolWindowContainer></HostContainerControl><AutoHide Dock="Left" /><AutoHide Dock="Right" /><AutoHide Dock="Top" /><AutoHide Dock="Bottom" /><TabbedDocuments Orientation="Horizontal" /><FloatingContainers /><Hidden /></LayoutData><CustomData /></ToolWindowLayout>
End_Of_Serialized_Data 3
End_Of_Object VDesktop 3
0
@ -659,7 +659,7 @@ VUniqueBox 4 Begin_Of_Object
VBoxRoot 5 Begin_Of_Object
1
3
0 2 0 1 -1 -1 -1 -1 22 22 1522 983
0 2 0 1 -1 -1 -1 -1 -227 39 1073 1000
1
@ -678,7 +678,7 @@ END_OF_DOCK_INFO
1582 856
END_OF_DESKTOP_DATA
6
0 1 -1 -1 -1 -1 22 22 1522 983
0 1 -1 -1 -1 -1 -227 39 1073 1000
6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 -1 32767 0 0 0 0 0 0 0 0 0 0 -1 -1 0 0 0 0 0 0
END_OF_DOCK_INFO
0
@ -686,7 +686,7 @@ END_OF_DOCK_INFO
0
0
1
999 592
1188 696
END_OF_DESKTOP_DATA
END_OF_DESKTOP_DATA_COLLECTION
0
@ -2516,7 +2516,7 @@ VUniqueBox 16 Begin_Of_Object
VBoxRoot 17 Begin_Of_Object
1
1
1 1 0 1 -1 -1 -1 -1 0 339 1188 688
1 1 0 1 -1 -1 -1 -1 0 285 1188 634
1
@ -2535,7 +2535,7 @@ END_OF_DOCK_INFO
999 591
END_OF_DESKTOP_DATA
6
0 1 -1 -1 -1 -1 0 339 1188 688
0 1 -1 -1 -1 -1 0 285 1188 634
6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 -1 32767 0 0 0 0 0 0 0 0 0 0 -1 -1 0 0 0 0 0 0
END_OF_DOCK_INFO
1
@ -2543,7 +2543,7 @@ END_OF_DOCK_INFO
0
0
1
1188 696
1188 634
END_OF_DESKTOP_DATA
END_OF_DESKTOP_DATA_COLLECTION
0
@ -2894,7 +2894,7 @@ VUniqueBox 4 Begin_Of_Object
VBoxRoot 5 Begin_Of_Object
1
3
0 0 0 1 -1 -1 -1 -1 0 0 1188 338
0 0 0 1 -1 -1 -1 -1 0 0 1188 284
1
@ -2913,7 +2913,7 @@ END_OF_DOCK_INFO
803 901
END_OF_DESKTOP_DATA
6
0 1 -1 -1 -1 -1 0 0 1188 338
0 1 -1 -1 -1 -1 0 0 1188 284
6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 -1 32767 0 0 0 0 0 0 0 0 0 0 -1 -1 0 0 0 0 0 0
END_OF_DOCK_INFO
1
@ -2921,7 +2921,7 @@ END_OF_DOCK_INFO
0
0
1
1188 696
1188 634
END_OF_DESKTOP_DATA
END_OF_DESKTOP_DATA_COLLECTION
0
@ -2961,7 +2961,7 @@ End_Of_Object VGrMnBox 3
VDOLocalInfoStruct 3 Begin_Of_Object
3
1
83
92
VDAOBus 4 Begin_Of_Object
1
1
@ -3024,7 +3024,7 @@ EOF_ASSEMBLYDATA
<VFileName V4 QL> 1 "include\CAPL\MakeConfig.cbf"
VIPBStackSetting 8 Begin_Of_Object
3
0
1
1
VIPBAdapterSetting 9 Begin_Of_Object
4
@ -3075,7 +3075,7 @@ End_Of_Serialized_Data 7
End_Of_Object VProgrammedNode 7
0
0
Startdelay 0 0 0
Startdelay 1 0 10
Jitter 0 0 1 0 0 0 0
1
1 ETHERNET_IL.DLL
@ -3091,7 +3091,7 @@ VSimulinkModelViewerConfiguration 7 Begin_Of_Object
End_Of_Object VSimulinkModelViewerConfiguration 7
1
0
3530245935
2517525802
0
NodeSignalPanelBustypeCount 0
End_Of_Object VSimulationNode 6
@ -3129,7 +3129,7 @@ NULL
End_Of_Object VDOLocalInfoStruct 3
0.000000
0 0
1 1 0 59420 1 233 1 2882400001 98 331 309 611 2882400002 0 0 0 0 0 0 1 2882400001 1270 1270 311 311 2882400002 0 0 0 1664573080 0 0 3
1 1 0 59420 1 233 1 2882400001 98 331 371 619 2882400002 0 0 0 0 0 0 1 2882400001 1270 1270 373 373 2882400002 0 0 0 339969840 0 409869284 3
SS_BEGIN_COMMON_INFO
1
0
@ -3141,7 +3141,7 @@ Ethernet
11
1
1
7573328 1 0 1 0 0 1 0 0 0 2000 1
340356552 1 0 1 0 0 1 0 0 71 2000 1
SS_BEGIN_COMMON_INFO
1
3
@ -3190,7 +3190,7 @@ END_OF_DOCK_INFO
END_OF_DESKTOP_DATA
6
0 1 -1 -1 0 0 0 700 662 1043
6 1 1010 180 0 0 300 180 300 180 0 61440 1 12180 1904 0 0 0 0 260 0 0 0 -1 -1 32767 0 59422 0 0 0 0 0 0 0 1 10 0 0 1 201 0 59419 1
6 1 1010 180 0 0 300 180 300 180 0 61440 1 12180 1904 0 0 0 0 260 0 0 0 -1 -1 32767 0 59422 0 0 0 0 0 0 0 1 10 0 0 1 263 0 59419 1
END_OF_DOCK_INFO
1
1
@ -3564,6 +3564,9 @@ End
FiltersEnd
0 0
END_OF_WORKSPACE_MEMBER_DATA
END_OF_WORKSPACE_MEMBER
1
@ -3684,7 +3687,7 @@ VIPBGlobalSettings 2 Begin_Of_Object
1
VIPBStackSetting 3 Begin_Of_Object
3
0
1
1
VIPBAdapterSetting 4 Begin_Of_Object
4

View File

@ -1,4 +1,4 @@
;CANoe Version |4|7|1|55201 ModbusNet
;CANoe Version |4|7|1|38816 ModbusNet
Version: 8.2.40 Build 40
32 PRO
10
@ -67,6 +67,7 @@ DialogBegin
1
285 569 816 1103
SymbolExplorerDialogBegin
1
HistoryBegin
1 0
@ -745,9 +746,11 @@ Begin_Of_Multi_Line_String
kPersistNoLineBreak
ey="{28077F35-C142-4ACC-B040-1BF0AB026C11}" Guid="ac9be154-bd12-4ff9-b255-03e05277dbe2" DockedSize="201, 281" FloatingLocation="111, 442" FloatingSize="1192, 514" HasOptions="False" ImageIndex="-1" Text="Trace" TitleBarText="Trace"><AutoHideStateInfo RootDock="Bottom" /><DockedInsideHostStateInfo RootDock="Bottom" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></ToolWindowContainer><ToolWindowContainer Dock="Top" Size="1192, 227" SelectedToolWindowGuid="7f29b491-3ada-4572-b140-b422651d6fed"><ToolWindow Key="{8F3DFCAC-9CCB-45C2-AF10-5DEC039B5956}" Guid="7f29b491-3ada-4572-b140-b422651d6fed" DockedSize="201, 223" FloatingLocation="6, 433" FloatingSize="300, 180" HasOptions="False" ImageIndex="-1" Text="Write" TitleBarText="Write"><AutoHideStateInfo RootDock="Top" /><DockedInsideHostStateInfo RootDock="Top" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></ToolWindowContainer></HostContainerControl><AutoHide Dock="Lef
kPersistNoLineBreak
t" /><AutoHide Dock="Right" /><AutoHide Dock="Top" /><AutoHide Dock="Bottom" /><TabbedDocuments Orientation="Horizontal" /><FloatingContainers /><Hidden><ToolWindow Key="{49714911-9568-49CC-A9CE-3B0905658C4A}" Guid="db27ffca-d17e-40f0-a70b-be70fe5eb4ec" State="DockableInsideHost" DockedSize="381, 0" FloatingLocation="1151, 79" FloatingSize="300, 180"><AutoHideStateInfo RootDock="Right" /><DockedInsideHostStateInfo RootDock="Right" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow><ToolWindow Key="{F5E09530-AAE7-48d9-B925-CEF5027AA97D}" Guid="b2726676-2b89-4fee-b1a3-2be7bfbdec73" State="DockableOutsideHost" DockedSize="176, 228" FloatingLocation="606, 459" FloatingSize="325, 380"><AutoHideStateInfo RootDock="Left" /><DockedInsideHostStateInfo RootDock="Left" IsAttached="False"><DockedBy Guid="87323901-fe96-4011-a82f-0e1b8627507a" RootDock="Left" DockOperationType="RightOuter" IsTopMost="True" /><DockedBy Guid="ac9be154-bd12-4ff9-b255-03e05277dbe2" RootD
t" /><AutoHide Dock="Right" /><AutoHide Dock="Top" /><AutoHide Dock="Bottom" /><TabbedDocuments Orientation="Horizontal" /><FloatingContainers /><Hidden><ToolWindow Key="{F5E09530-AAE7-48d9-B925-CEF5027AA97D}" Guid="b2726676-2b89-4fee-b1a3-2be7bfbdec73" State="DockableOutsideHost" DockedSize="176, 228" FloatingLocation="518, 238" FloatingSize="325, 380" HasOptions="False" ImageIndex="-1" Text="Symbol Explorer" TitleBarText="Symbol Explorer"><AutoHideStateInfo RootDock="Left" /><DockedInsideHostStateInfo RootDock="Left" IsAttached="False"><DockedBy Guid="87323901-fe96-4011-a82f-0e1b8627507a" RootDock="Left" DockOperationType="RightOuter" IsTopMost="True" /><DockedBy Guid="ac9be154-bd12-4ff9-b255-03e05277dbe2" RootDock="Bottom" DockOperationType="TopOuter" IsTopMost="True" /><DockedBy Guid="7f29b491-3ada-4572-b140-b422651d6fed" RootDock="Top" DockOperationType="BottomOuter" IsTopMost="True" /></DockedInsideHostStateInfo><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow><Tool
kPersistNoLineBreak
ock="Bottom" DockOperationType="TopOuter" IsTopMost="True" /><DockedBy Guid="7f29b491-3ada-4572-b140-b422651d6fed" RootDock="Top" DockOperationType="BottomOuter" IsTopMost="True" /></DockedInsideHostStateInfo><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow><ToolWindow Key="{224229E5-10CA-425F-8B6F-C213CD04C44F}" Guid="859d3aae-7aff-47f2-8ca4-bb7d01f32282" State="DockableInsideHost" DockedSize="424, 358" FloatingLocation="347, 323" FloatingSize="890, 483"><AutoHideStateInfo RootDock="Right" /><DockedInsideHostStateInfo RootDock="Right" IsAttached="False"><DockedBy Guid="ac9be154-bd12-4ff9-b255-03e05277dbe2" RootDock="Bottom" DockOperationType="TopOuter" IsTopMost="True" /><DockedBy Guid="7f29b491-3ada-4572-b140-b422651d6fed" RootDock="Top" DockOperationType="BottomOuter" IsTopMost="True" /></DockedInsideHostStateInfo><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></Hidden></LayoutData><CustomData /></ToolWindowLayout>
Window Key="{49714911-9568-49CC-A9CE-3B0905658C4A}" Guid="db27ffca-d17e-40f0-a70b-be70fe5eb4ec" State="DockableInsideHost" DockedSize="381, 0" FloatingLocation="1151, 79" FloatingSize="300, 180"><AutoHideStateInfo RootDock="Right" /><DockedInsideHostStateInfo RootDock="Right" IsAttached="False" /><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow><ToolWindow Key="{224229E5-10CA-425F-8B6F-C213CD04C44F}" Guid="859d3aae-7aff-47f2-8ca4-bb7d01f32282" State="DockableInsideHost" DockedSize="424, 358" FloatingLocation="347, 323" FloatingSize="890, 483"><AutoHideStateInfo RootDock="Right" /><DockedInsideHostStateInfo RootDock="Right" IsAttached="False"><DockedBy Guid="ac9be154-bd12-4ff9-b255-03e05277dbe2" RootDock="Bottom" DockOperationType="TopOuter" IsTopMost="True" /><DockedBy Guid="7f29b491-3ada-4572-b140-b422651d6fed" RootDock="Top" DockOperationType="BottomOuter" IsTopMost="True" /></DockedInsideHostStateInfo><DockedOutsideHostStateInfo IsAttached="False" /></ToolWindow></Hidd
kPersistNoLineBreak
en></LayoutData><CustomData /></ToolWindowLayout>
End_Of_Serialized_Data 3
End_Of_Object VDesktop 3
VDesktop 3 Begin_Of_Object
@ -1192,7 +1195,7 @@ End_Of_Serialized_Data 15
End_Of_Object VPredefinedSignalObject 15
[MeasurementObject]
Eth 1::Rx Bus load
"%" 1 800080 0. 100. -100. 100. 10 -5 0 0 36000000 0 1 0 0
"%" 1 800080 0. 100. -100. 100. 10 -5 0 0 36000000 1 1 0 0
VPredefinedSignalObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1243,7 +1246,7 @@ End_Of_Serialized_Data 15
End_Of_Object VPredefinedSignalObject 15
[MeasurementObject]
Eth 1::Tx Bus load
"%" 1 80 0. 100. -100. 100. 10 -5 0 0 36000000 0 1 0 0
"%" 1 80 0. 100. -100. 100. 10 -5 0 0 36000000 1 1 0 0
VPredefinedSignalObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1379,7 +1382,7 @@ End_Of_Serialized_Data 15
End_Of_Object VSysVarObject 15
[MeasurementObject]
Client_2::InputBits_[0]
"" 223 b86b8 -1. 1. -100. 100. 1 0 0 0 36000000 1 1 0 0
"" 223 b86b8 -1. 1. -100. 100. 1 0 0 0 36000000 0 1 0 0
VSysVarObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1413,7 +1416,7 @@ End_Of_Serialized_Data 15
End_Of_Object VSysVarObject 15
[MeasurementObject]
Client_2::InputBits_[1]
"" 223 d7ff -1. 1. -100. 100. 1 0 0 0 36000000 1 1 0 0
"" 223 d7ff -1. 1. -100. 100. 1 0 0 0 36000000 0 1 0 0
VSysVarObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1447,7 +1450,7 @@ End_Of_Serialized_Data 15
End_Of_Object VSysVarObject 15
[MeasurementObject]
Client_3::InputBits_[5]
"" 223 9314ff 0. 1. -100. 100. 1 0 0 0 36000000 1 1 0 0
"" 223 9314ff 0. 1. -100. 100. 1 0 0 0 36000000 0 1 0 0
VSysVarObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1481,7 +1484,7 @@ End_Of_Serialized_Data 15
End_Of_Object VSysVarObject 15
[MeasurementObject]
Client_2::InputRegisters_[0]
"" 223 ff00 2746. 6071. -100. 100. 500 0 0 0 36000000 1 1 0 0
"" 223 ff00 2746. 6071. -100. 100. 500 0 0 0 36000000 0 1 0 0
VSysVarObject 15 Begin_Of_Object
1
VHostSignal 16 Begin_Of_Object
@ -1515,9 +1518,9 @@ End_Of_Serialized_Data 15
End_Of_Object VSysVarObject 15
[MeasurementObject]
Client_3::InputRegisters_[3]
"" 223 228b22 8987. 19894. -100. 100. 1000 0 0 0 36000000 1 1 0 0
"" 223 228b22 8987. 19894. -100. 100. 1000 0 0 0 36000000 0 1 0 0
[GraphWindow:x_x_x_x_x_x_WindowBk_Grid_AxisBk_XAxisFr_YAxisFr_x_x_x_x_x_x]
5210346.6428800002 5634098.14585 240215.71337000001 200000 36000000 1 ffffff b2b2b2 ffffff 0 0 0 0 1 1 1 0
0 423751.50296999997 423751.50296999997 200000 36000000 1 ffffff b2b2b2 ffffff 0 0 0 0 1 1 1 0
0 30 5000
0
0 100
@ -1528,7 +1531,7 @@ Client_3::InputRegisters_[3]
0
1
41943040
8
1
1416 25200245
Grafik-Fenster
<VFileName V4 QL> 1 ""
@ -1773,7 +1776,7 @@ End_Of_Object VTraceAnalysisSingleFilter 17
1
End_Of_Object VTraceAnalysisFilterGroup 16
End_Of_Object VTraceFilterCfg 15
1
0
1
0
0
@ -1796,7 +1799,7 @@ End_Of_Serialized_Data 14
6
1
14
ver=2: FT TF TF FF FT FF;F T Config;F T Ethernet1;F T GLLogger;T F _Statistics
ver=2: FT TF TF FT FT FT;F T Config;F T Ethernet1;F T GLLogger;T F _Statistics
End_Of_Serialized_Data 14
7
0
@ -1837,29 +1840,29 @@ End_Of_Serialized_Data 14
22
1
14
ver=2: FF
ver=2: FT
End_Of_Serialized_Data 14
23
1
14
ver=2: FF
ver=2: FT
End_Of_Serialized_Data 14
24
1
14
ver=2: FF
ver=2: FT
End_Of_Serialized_Data 14
25
0
26
1
14
ver=2: FF
ver=2: FT
End_Of_Serialized_Data 14
27
1
14
ver=2: FF
ver=2: FT
End_Of_Serialized_Data 14
0
2
@ -3734,7 +3737,7 @@ End_Of_Serialized_Data 14
0
0
290
1
0
160
<VFileName V4 QL> 1 ""
End_Of_Object VTraceControlCfg 14
@ -4471,7 +4474,7 @@ End_Of_Serialized_Data 14
End_Of_Object VSysVarObject 14
[Begin_of_Item]
2 18
1 1 10 0 0 16777215
1 1 3 0 0 16777215
0 1000 0 0
[End_of_Item]
VSysVarObject 14 Begin_Of_Object
@ -4543,7 +4546,7 @@ End_Of_Serialized_Data 14
End_Of_Object VSysVarObject 14
[Begin_of_Item]
2 17
1 1 2 0 0 16777215
1 1 3 0 0 16777215
0 1000 0 0
[End_of_Item]
VSysVarObject 14 Begin_Of_Object
@ -4879,7 +4882,7 @@ END_OF_DOCK_INFO
0
0
1
0 1 -32088 -32000 -1 -1 147 402 1041 893
0 1 -1 -1 -1 -1 -117 402 777 893
0
1
776 389
@ -4933,7 +4936,7 @@ End_Of_Object VGrMnBox 3
VDOLocalInfoStruct 3 Begin_Of_Object
3
1
218
228
VDAOBus 4 Begin_Of_Object
1
1
@ -5045,7 +5048,7 @@ End_Of_Object VIPBStackSetting 8
NDebugger::VDebuggerHost 8 Begin_Of_Object
2
0
26
27
NDebugger::VFile 9 Begin_Of_Object
1
<VFileName V4 QL> 1 "ModbusTcpCommon.cin"
@ -5176,6 +5179,11 @@ NDebugger::VFile 9 Begin_Of_Object
<VFileName V4 QL> 1 "include\CAPL\include\ModbusStructs.cin"
42
End_Of_Object NDebugger::VFile 9
NDebugger::VFile 9 Begin_Of_Object
1
<VFileName V4 QL> 1 "include\CAPL\include\DeviceInformation.cin"
43
End_Of_Object NDebugger::VFile 9
VNETStandaloneComponent 9 Begin_Of_Object
1
VNETControlBox 10 Begin_Of_Object
@ -5409,7 +5417,7 @@ VSimulinkModelViewerConfiguration 7 Begin_Of_Object
End_Of_Object VSimulinkModelViewerConfiguration 7
1
0
3569770309
2097296340
0
NodeSignalPanelBustypeCount 0
End_Of_Object VSimulationNode 6
@ -5544,7 +5552,7 @@ VSimulinkModelViewerConfiguration 7 Begin_Of_Object
End_Of_Object VSimulinkModelViewerConfiguration 7
1
0
3569770309
2097296340
0
NodeSignalPanelBustypeCount 0
End_Of_Object VSimulationNode 6
@ -5803,7 +5811,7 @@ NULL
End_Of_Object VDOLocalInfoStruct 3
0.000000
0 0
1 1 0 59420 1 176 1 2882400001 243 443 430 885 2882400002 0 0 0 0 0 20 1 2882400001 1121 1321 432 632 2882400002 0 0 0 0 0 0 3
1 1 0 59420 1 176 1 2882400001 -21 179 430 885 2882400002 0 0 0 0 0 20 1 2882400001 857 1057 432 632 2882400002 0 0 0 0 0 0 3
SS_BEGIN_COMMON_INFO
1
0
@ -6217,14 +6225,14 @@ SymbSelHeaderMgrBegin
SymbSelHeaderMgrEnd
End
Begin
3 0 -1
3 8 16
3
Modbus
modbus
Systemvariablen
( 3 ( 0 ) 0 )
( 3 ( 1 ( 3 ( 0 ) 0 ) 0 ) 0 )
SymbSelHeaderMgrBegin
1 4
0 1 200 0 0

View File

@ -1,23 +1,12 @@
/*@!Encoding:1252*/
includes
{
#include "include/DeviceInformation.cin"
#include "include/ModbusUdpClientCommon.cin"
#include "include/ModbusFunctions.cin"
}
variables
{
struct device // A structure that contains information about an Modbus device
{
char Ip[16]; // String: The IP address
char IpLsb[4]; // String: The last byte of the IP address. Used as index of node name
char IpNet[4]; // String: The second last byte of the IP. Used as index of net
enum Vendor Vendor; // The Vendor (Wago / B&R)
word SerialCode; // Serial Code
word DeviceCode; // Device Code
struct deviceIOs DeviceIOs; // A structure with more information about IOs
};
char[16] gIps[long]; // List IP addresses. These will be analysed
char gScanFirstIp[16]; // The first IP address that will be scanned
char gScanLastIp[16]; // The first IP address that will not be scanned anymore.
@ -46,10 +35,10 @@ on preStart
strncpy(gIps[3], "192.168.1.8", 16);
*/
// Scan a range of IPs for devices. Start and Stop go here
// Please note: Currently .255 will be skipped! Don't use it for devices and as stop address
// Scan a range of IPs for devices (if nothing was set above). Start and Stop go here
// Please note: Currently .255 will be skipped! Don't use it for devices
strncpy(gScanFirstIp, "192.168.1.2", 16);
strncpy(gScanLastIp, "192.168.1.10", 16);
strncpy(gScanLastIp, "192.168.1.20", 16);
// Name of the project
strncpy(name, "Modbus", elCount(name));
@ -58,7 +47,7 @@ on preStart
strncpy(fnSysvar, "include/SysVars/generated.vsysvar", elCount(fnSysvar));
strncpy(fnDbc, "include/DBC/generated.dbc", elCount(fnDbc));
OutputDebugLevel = Mute;
OutputDebugLevel = Error;
}
on start
@ -99,10 +88,10 @@ void DetectDevices()
{
write("Scanning from %s to %s with timeout of %d ms", gScanFirstIp, gScanLastIp, @sysvar::Config::Modbus::RequestTimeout);
gScanFirst = ipGetAddressAsNumber(gScanFirstIp);
gScanLast = ipGetAddressAsNumber(gScanLastIp);
gScanFirst = ipGetAddressAsNumber(gScanFirstIp); // We have to use big endian here
gScanLast = swapDWord(ipGetAddressAsNumber(gScanLastIp)); // But not here :)
write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
ModbusConnectTo(gScanFirst, @sysvar::Config::Modbus::Port); // Open socket and set variables
ModbusReadBits(0, 1); // Start device detection
}
@ -110,52 +99,36 @@ void DetectDevices()
/// <Step1>
void DetectDevicesNext()
{
// next IP
// Note: IP address is stored as big endian, comments are notated as little endian :)
// 0xFE...... --> Skip xxx.xxx.xxx.255 which is broadcast address in 192.168.xxx.0 nets
// If first three bytes are full (123.255.255.255), set those to 0 and increment the first byte (124.0.0.0)
if ((gScanFirst & 0xFFFFFF00) == 0xFEFFFF00)
gScanFirst = swapDWord(gScanFirst);
gScanFirst++;
if ((gScanFirst & 0xFF) == 0xFF) // .255
{
gScanFirst &= 0x000000FF;
gScanFirst += 0x00000001;
write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
}
// If first two bytes are full (124.111.255.255), set those to 0 and increment the second byte (124.112.0.0)
else if ((gScanFirst & 0xFFFF0000) == 0xFEFF0000)
{
gScanFirst &= 0x0000FFF;
gScanFirst += 0x00000100;
write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
}
// If first last byte is full (124.112.222.255), set it to 0 and increment the third byte (124.112.223.0)
else if ((gScanFirst & 0xFF000000) == 0xFE000000)
{
gScanFirst &= 0x00FFFFFF;
gScanFirst += 0x00010000;
write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
}
// Else simply increment the LSB
else
{
gScanFirst += 0x01000000;
gScanFirst++;
writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst >> 24, (gScanFirst >> 16) & 0xFF, (gScanFirst >> 8) & 0xFF, gScanFirst & 0xFF);
}
if (gScanFirst == gScanLast) // If this is the last address we stop the detection
if (gScanFirst > gScanLast)
{
@sysvar::Config::Modbus::MaxTransmissionCount = gMaxTransmissionCount;
MakeIpNets();
return;
}
writeEx(1, 1, "."); // Write something so the user knows something is happening
gScanFirst = swapDWord(gScanFirst);
writeEx(0, 0, "."); // Write something so the user knows something is happening
gRemoteIP = gScanFirst; // Don't open new socket, it takes too much time. This means we should use UDP here!
ModbusReadBits(0, 1); // Scan the next device
}
/// <Step1>
void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap)
{
DetectDevicesNext(); // Timeout! We will go to the next device
switch (error)
{
case FinalTimeout:
case Exception:
DetectDevicesNext(); // Timeout! We will go to the next device
}
}
/// <Step1>
void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[], struct ModbusReqRead mbreq)
@ -250,21 +223,11 @@ void OnModbusReadRegistersFailed(enum ModbusRequestError error, enum ModbusExcep
case Exception:
memcpy_n2h(mbreq, gQueueAck[mbap.TxID].Buffer);
if (mbreq.Address == 0x1000 && ex == IllegalDataAddress) // We requested Wago SerialCode and it didn't work --> Not Wago --> B&R
if (mbreq.Address == 0x1000 && ex == IllegalDataAddress) // We requested Wago SerialCode and it didn't work --> Not Wago --> B&R. Not future proof
{
gIpsSorted[ips[ADi]].Vendor = Wago;
// request information
ADn = 10;
ModbusReadRegisters(0x2011, 1); // Serial Code
ModbusReadRegisters(0x2012, 1); // Device Code
ModbusReadRegisters(0x1022, 1); // Number of AOs (= size in bits)
ModbusReadRegisters(0x1023, 1); // Number of AIs (= size in bits)
ModbusReadRegisters(0x1024, 1); // Number of DOs
ModbusReadRegisters(0x1025, 1); // Number of DIs
ModbusReadRegisters(0x2030, 65); // Connected IO 1
ModbusReadRegisters(0x2031, 64); // Connected IO 2
ModbusReadRegisters(0x2032, 64); // Connected IO 3
ModbusReadRegisters(0x2033, 63); // Connected IO 4
ADn = DeviceGetInformation(Wago);
return;
}
@ -283,83 +246,16 @@ void OnModbusReadRegistersFailed(enum ModbusRequestError error, enum ModbusExcep
/// <Step3>
void OnModbusReadRegistersSuccess(struct ModbusResReceiveRegisters mbres, struct ModbusReqRead mbreq)
{
byte i;
if (mbreq.Address == 0x1000) // We detected a B&R device
{
gIpsSorted[ips[ADi]].Vendor = BuR;
// request further information
ADn = 5;
ModbusReadRegisters(0x1083, 1); // Product Code
ModbusReadRegisters(0x1101, 1); // Number of AIs
ModbusReadRegisters(0x1103, 1); // Number of AOs
ModbusReadRegisters(0x1105, 1); // Number of DIs
ModbusReadRegisters(0x1107, 1); // Number of DOs
ADn = DeviceGetInformation(BuR);
return;
}
switch (gIpsSorted[ips[ADi]].Vendor)
{
case Wago:
// Parse the received data
switch (mbreq.Address)
{
case 0x2011:
gIpsSorted[ips[ADi]].serialCode = mbres.Data[0];
break;
case 0x2012:
gIpsSorted[ips[ADi]].deviceCode = mbres.Data[0];
break;
case 0x1022:
gIpsSorted[ips[ADi]].DeviceIOs.OutputRegisters = mbres.Data[0] / 16;
break;
case 0x1023:
gIpsSorted[ips[ADi]].DeviceIOs.InputRegisters = mbres.Data[0] / 16;
break;
case 0x1024:
gIpsSorted[ips[ADi]].DeviceIOs.OutputBits = mbres.Data[0];
break;
case 0x1025:
gIpsSorted[ips[ADi]].DeviceIOs.InputBits = mbres.Data[0];
break;
case 0x2030:
case 0x2031:
case 0x2032:
case 0x2033:
for (i = 0; i < mbreq.Count; i++)
{
if (mbres.Data[i] == 0x0000) // No more devices --> end
break;
ParseDeviceCode(mbres.Data[i], gIpsSorted[ips[ADi]].Vendor, gIpsSorted[ips[ADi]].DeviceIOs);
}
break;
}
break;
case BuR:
// Parse the received data
switch (mbreq.Address)
{
case 0x1083:
gIpsSorted[ips[ADi]].serialCode = mbres.Data[0];
break;
case 0x1101:
gIpsSorted[ips[ADi]].DeviceIOs.InputRegisters = mbres.Data[0] - 3; // X20BC0087 has 3 AIs when no module is connected... hö?
break;
case 0x1103:
gIpsSorted[ips[ADi]].DeviceIOs.OutputRegisters = mbres.Data[0];
break;
case 0x1105:
gIpsSorted[ips[ADi]].DeviceIOs.InputBits = mbres.Data[0] * 8; // Unfortunately this is quite imprecise:
// in the process image one module will always fill a whole number of bytes.
// So 4 12DI modules not allocate not 4*12 bit = 6 byte, but 4*16 bit = 64 bit = 8 byte
break;
case 0x1107:
gIpsSorted[ips[ADi]].DeviceIOs.OutputBits = mbres.Data[0] * 8;
break;
}
break;
}
DeviceParseRegister(gIpsSorted[ips[ADi]], mbreq.Address, mbres.Data, mbreq.Count);
if (--ADn == 0) // If we received all registers
AnalyzeDevicesNext();
@ -414,9 +310,9 @@ void GenSysvars()
for (long ipN : gIpsSorted)
{
if (((ipN >> 16) & 0xFF) != net)
continue;
DeviceInit(gIpsSorted[ipN].Vendor);
PutString(" <namespace name=\"Client_");
//PutString(netS);
@ -463,74 +359,34 @@ void GenSysvars()
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"InputRegisters\" comment=\"Number of input registers\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputRegisters);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
if (gIpsSorted[ipN].Vendor == Wago)
{
PutString((word)123);
PutString("\" maxValuePhys=\"");
PutString((word)123);
PutString("\" />\n");
}
else
{
PutString((word)2048);
PutString("\" maxValuePhys=\"");
PutString((word)2048);
PutString("\" />\n");
}
PutString((word)gDevRegMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevRegMaxCount);
PutString("\" />\n");
// InputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"InputBits\" comment=\"Number of input bits\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputBits);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
if (gIpsSorted[ipN].Vendor == Wago)
{
PutString((word)2000);
PutString("\" maxValuePhys=\"");
PutString((word)2000);
PutString("\" />\n");
}
else
{
PutString((word)16384);
PutString("\" maxValuePhys=\"");
PutString((word)16384);
PutString("\" />\n");
}
PutString((word)gDevBitMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevBitMaxCount);
PutString("\" />\n");
// OutputRegisters
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"OutputRegisters\" comment=\"Number of output registers\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputRegisters);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
if (gIpsSorted[ipN].Vendor == Wago)
{
PutString((word)123);
PutString("\" maxValuePhys=\"");
PutString((word)123);
PutString("\" />\n");
}
else
{
PutString((word)2048);
PutString("\" maxValuePhys=\"");
PutString((word)2048);
PutString("\" />\n");
}
PutString((word)gDevRegMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevRegMaxCount);
PutString("\" />\n");
// OutputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"OutputBits\" comment=\"Number of output bits\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputBits);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
if (gIpsSorted[ipN].Vendor == Wago)
{
PutString((word)2000);
PutString("\" maxValuePhys=\"");
PutString((word)2000);
PutString("\" />\n");
}
else
{
PutString((word)16384);
PutString("\" maxValuePhys=\"");
PutString((word)16384);
PutString("\" />\n");
}
PutString((word)gDevBitMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevBitMaxCount);
PutString("\" />\n");
PutString(" </namespace>\n");
// Namespace Data

View File

@ -3,6 +3,7 @@
includes
{
#include "include\ModbusUdpClientCommon.cin"
#include "include\DeviceInformation.cin"
}
variables
@ -10,51 +11,20 @@ variables
msTimer gtRead;
}
// Get information of local network interface such like ip address
on preStart
{
writeClear(0);
setStartdelay(10);
OutputDebugLevel = Warning;
OutputDebugLevel = MbDebug;
}
on start
{
word outputBits, outputRegs, outputBitAddr, outputRegAddr;
outputBits = @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputBits;
outputRegs = @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputRegisters;
switch ((enum Vendor)@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::Vendor)
{
case Wago:
outputBitAddr = 0x200;
outputRegAddr = 0x200;
break;
case BuR:
outputBitAddr = 0x000;
outputRegAddr = 0x800;
break;
}
DeviceInit(@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::Vendor);
ModbusInit();
while (outputBits > 0)
{
ModbusReadOutBits(outputBitAddr, 2000);
outputBits -= 2000;
}
if (outputBits > 0)
ModbusReadOutBits(outputBitAddr, outputBits);
while (outputRegs > 0)
{
ModbusReadRegisters(outputRegAddr, 123);
outputRegs -= 123;
}
if (outputRegs > 0)
ModbusReadRegisters(outputRegAddr, outputRegs);
ModbusReadOutBits(gDevOutputBitAddr, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputBits);
ModbusReadOutRegisters(gDevOutputRegAddr, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputRegisters);
setTimerCyclic(gtRead, 1, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Config::Interval);
}
@ -121,20 +91,28 @@ void OnModbusWriteRegistersFailed(enum ModbusRequestError error, enum ModbusExce
void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[], struct ModbusReqRead mbreq)
{
word i;
word i, offset;
switch(mbreq.Address)
switch (mbres.Header.FuncCode) // We assume that we separate between 0x01 and 0x02 even though the address space may be the same
{
case 0x200: // set output bits
case 0x01: // Read output bits
sysBeginVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "OutputBits");
for (i = 0; i < @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputBits; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::OutputBits[i] = bitStatus[i];
offset = mbreq.Address - gDevOutputBitAddr; // Get the offset to the base output bit address
for (i = 0; i < mbreq.Count; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::OutputBits[i + offset] = bitStatus[i];
sysEndVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "OutputBits");
break;
default: // set input bits
case 0x02: // Read input bits
sysBeginVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "InputBits");
for (i = 0; i < @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputBits; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::InputBits[i] = bitStatus[i];
offset = mbreq.Address - gDevInputBitAddr; // Get the offset to the base input bit address
for (i = 0; i < mbreq.Count; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::InputBits[i + offset] = bitStatus[i];
sysEndVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "InputBits");
break;
}
@ -142,29 +120,30 @@ void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[]
void OnModbusReadRegistersSuccess(struct ModbusResReceiveRegisters mbres, struct ModbusReqRead mbreq)
{
char str[20*5];
long fehler;
byte i;
word i, offset;
switch (mbreq.Address)
switch (mbres.Header.FuncCode) // We assume that we separate between 0x03 and 0x04 even though the address space may be the same
{
case 0x200: // set output registers
case 0x03: // Read output registers
sysBeginVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "OutputRegisters");
for (i = 0; i < @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::OutputRegisters; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::OutputRegisters[i] = mbres.Data[i];
offset = mbreq.Address - gDevOutputRegAddr; // Get the offset to the base output register address
for (i = 0; i < mbreq.Count; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::OutputRegisters[i + offset] = mbres.Data[i];
sysEndVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "OutputRegisters");
break;
case 0x000: // set input registers
case 0x04: // Read input registers
sysBeginVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "InputRegisters");
for (i = 0; i < @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputRegisters; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::InputRegisters[i] = mbres.Data[i];
offset = mbreq.Address - gDevInputRegAddr; // Get the offset to the base input bit address
for (i = 0; i < mbreq.Count; i++)
@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::InputRegisters[i + offset] = mbres.Data[i];
sysEndVariableStructUpdate("%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data", "InputRegisters");
break;
default:
// Not recognized
dbin_to_strhex(mbres.Data, str);
writeLineEx(0, 1, "<%NODE_NAME%> OnModbusReceiveRegisters: Received %d bytes at 0x%04X: %s", mbres.ByteCount, mbreq.Address, str);
break;
}
}
@ -204,10 +183,9 @@ void OnModbusClientPanics(enum FatalErrors reason)
// Key events -------------------------------------------------------------------------
on timer gtRead
{
if (@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputRegisters > 0)
ModbusReadRegisters(0x0000, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputRegisters);
if (@sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputBits > 0)
ModbusReadBits(0x0000, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputBits);
ModbusReadRegisters(0x0000, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputRegisters);
ModbusReadBits(0x0000, @sysvar::%BUS_TYPE%%CHANNEL%::%NODE_NAME%::Info::InputBits);
this.Cancel();
}
on sysvar %BUS_TYPE%%CHANNEL%::%NODE_NAME%::Data::OutputBits

View File

@ -0,0 +1,285 @@
/*@!Encoding:1252*/
variables
{
word gDevOutputBitAddr, gDevOutputRegAddr;
word gDevInputBitAddr, gDevInputRegAddr;
word gDevBitMaxCount, gDevRegMaxCount;
word gDevReceiveWindow;
enum Vendor
{
Wago = 23,
BuR = 2
};
struct deviceIOs
{
byte InputRegisters;
word InputBits;
byte OutputRegisters;
word OutputBits;
char Modules[1024];
};
struct device // A structure that contains information about an Modbus device
{
char Ip[16]; // String: The IP address
char IpLsb[4]; // String: The last byte of the IP address. Used as index of node name
char IpNet[4]; // String: The second last byte of the IP. Used as index of net
enum Vendor Vendor; // The Vendor (Wago / B&R)
word SerialCode; // Serial Code
word DeviceCode; // Device Code
struct deviceIOs DeviceIOs; // A structure with more information about IOs
};
}
// This is for the normal client and for making the sysvars
/// <ModbusClient>
void DeviceInit(byte vendor)
{
switch ((enum Vendor) vendor)
{
case Wago:
gDevInputBitAddr = 0x0000; // Wago inputs start at 0x000
gDevInputRegAddr = 0x0000;
gDevOutputBitAddr = 0x0200; // Wago outputs start at 0x200
gDevOutputRegAddr = 0x0200;
gDevBitMaxCount = 0x0100; // Wago allows up to 256 inputs
gDevRegMaxCount = 0x0100;
gDevReceiveWindow = 5; // Wago can handle 5 requests simultaneously
break;
case BuR:
gDevInputBitAddr = 0x0000; // B&R inputs start at 0x000
gDevInputRegAddr = 0x0000;
gDevOutputBitAddr = 0x0000; // B&R digital outputs start at 0x000
gDevOutputRegAddr = 0x0800; // B&R analog outputs start at 0x800
gDevBitMaxCount = 0x4000; // B&R allows up to 16348 digital inputs
gDevRegMaxCount = 0x0800; // B&R allows up to 2048 analog inputs
gDevReceiveWindow = 1; // B&R can only handle 1 request at a time
break;
}
}
// This is for making the sysvars (MakeConfig)
/// <MakeConfig>
void DeviceParseCode(word dev, enum Vendor vendor, struct deviceIOs dios)
{
byte input;
byte numChannels;
char module[10];
switch(vendor)
{
case Wago: // if this is a Wago device
if (dev & 0x8000) // Digital Module
{
numChannels = (dev >> 8) & 0x007F;
if (dev & 0x0001) // Input Module
{
input = 1;
strncpy(module, "DI%d,", elCount(module));
}
else if (dev & 0x0002) // Output Module
{
input = 0;
strncpy(module, "DO%d,", elCount(module));
}
else // mhm. What is it?
{
writeDbg(AlgoError, "ParseDeviceCode: Device code 0x%X cannot be decoded", dev);
OnModbusClientPanics(DeviceCodeUnknown);
}
}
else
{
// http://www.wago.com/wagoweb/documentation/navigate/nm0dx__d.htm
// http://www.wago.com/wagoweb/documentation/navigate/nm0dy__d.htm
switch (dev)
{
case 881: // devices that have no inputs/outputs
return;
case 491: // devices that have 1 inputs
input = 1;
numChannels = 1;
break;
case 452: // devices that have 2 inputs
case 465:
case 470:
case 472:
case 480:
case 454:
case 473:
case 474:
case 466:
case 484:
case 485:
case 492:
case 482:
case 475:
case 467:
case 477:
case 478:
case 456:
case 479:
case 476:
case 483:
case 461:
case 481:
case 462:
case 469:
case 487:
input = 1;
numChannels = 2;
break;
case 493: // devices that have 3 inputs
case 494:
case 495:
input = 1;
numChannels = 3;
break;
case 459: // devices that have 4 inputs
case 453:
case 455:
case 468:
case 457:
case 464:
case 460:
case 463:
input = 1;
numChannels = 4;
break;
case 552: // devices that have 2 inputs
case 585:
case 563:
case 554:
case 550:
case 560:
case 562:
case 556:
input = 0;
numChannels = 2;
case 555: // devices that have 4 inputs
case 553:
case 557:
case 559:
input = 0;
numChannels = 4;
default: // unknown device. Ouch!
writeDbg(AlgoInfo, "Connected device: 750-%d", dev);
return;
}
if (input)
strncpy(module, "AI%d,", elCount(module));
else
strncpy(module, "AO%d,", elCount(module));
}
break; // switch(vendor)
default:
writeDbg(AlgoError, "ParseDeviceCode: Unknown vendor id: %d", vendor);
OnModbusClientPanics(VendorIdUnknown);
return;
}
snprintf(module, elCount(module), module, numChannels);
strncat(dios.Modules, module, elCount(dios.Modules));
}
// This function requests more information from the device and return the number of expected results
/// <MakeConfig>
byte DeviceGetInformation(enum Vendor vendor)
{
switch (vendor)
{
case Wago:
ModbusReadRegisters(0x2011, 1); // Serial Code
ModbusReadRegisters(0x2012, 1); // Device Code
ModbusReadRegisters(0x1022, 1); // Number of AOs (= size in bits)
ModbusReadRegisters(0x1023, 1); // Number of AIs (= size in bits)
ModbusReadRegisters(0x1024, 1); // Number of DOs
ModbusReadRegisters(0x1025, 1); // Number of DIs
ModbusReadRegisters(0x2030, 65); // Connected IO 1
ModbusReadRegisters(0x2031, 64); // Connected IO 2
ModbusReadRegisters(0x2032, 64); // Connected IO 3
ModbusReadRegisters(0x2033, 63); // Connected IO 4
return 10;
case BuR:
ModbusReadRegisters(0x1083, 1); // Product Code
ModbusReadRegisters(0x1101, 1); // Number of AIs
ModbusReadRegisters(0x1103, 1); // Number of AOs
ModbusReadRegisters(0x1105, 1); // Number of DIs
ModbusReadRegisters(0x1107, 1); // Number of DOs
return 5;
}
return 0;
}
// This function parses the received registers
/// <MakeConfig>
void DeviceParseRegister(struct device device, word address, word data[], word count)
{
byte i;
switch (device.Vendor)
{
case Wago:
// Parse the received data
switch (address)
{
case 0x2011:
device.serialCode = data[0];
break;
case 0x2012:
device.deviceCode = data[0];
break;
case 0x1022:
device.DeviceIOs.OutputRegisters = data[0] / 16; // Wago returns the size in bits allocated by the module
break;
case 0x1023:
device.DeviceIOs.InputRegisters = data[0] / 16;
break;
case 0x1024:
device.DeviceIOs.OutputBits = data[0];
break;
case 0x1025:
device.DeviceIOs.InputBits = data[0];
break;
case 0x2030:
case 0x2031:
case 0x2032:
case 0x2033:
for (i = 0; i < count; i++)
{
if (data[i] == 0x0000) // No more devices --> end
break;
DeviceParseCode(data[i], device.Vendor, device.DeviceIOs);
}
break;
}
break;
case BuR:
// Parse the received data
switch (address)
{
case 0x1083:
device.serialCode = data[0];
break;
case 0x1101:
device.DeviceIOs.InputRegisters = data[0];
break;
case 0x1103:
device.DeviceIOs.OutputRegisters = data[0];
break;
case 0x1105:
device.DeviceIOs.InputBits = data[0] * 8; // Unfortunately this is quite imprecise:
// in the process image one module will always fill a whole number of bytes.
// So 4 12DI modules not allocate not 4*12 bit = 6 byte, but 4*16 bit = 64 bit = 8 byte
// See Modbus X20BC0087 documentation v1.11 p. 22
break;
case 0x1107:
device.DeviceIOs.OutputBits = data[0] * 8;
break;
}
break;
}
}

View File

@ -7,8 +7,6 @@ includes
variables
{
const word gMaxPacketLength = __size_of(struct ModbusReqWriteRegisters);
msTimer gtRobin; // Timer that sends the packets and watches for timeouts
word gTxID = 0x0000; // Transaction Identifier for Modbus. Used as index for gQueue
@ -18,7 +16,7 @@ variables
word TimeoutTicks;
byte Timeouts;
word Length;
byte Buffer[gMaxPacketLength];
byte Buffer[gModbusMaxTelegramSize];
};
struct QueueElement gQueuePending[long, 2];
struct QueueElement gQueueSent[long, 2];
@ -48,49 +46,58 @@ void ModbusInit()
ModbusConnectTo(ip, @sysvar::Config::Modbus::Port);
}
void ModbusMakeHeader(struct ModbusApHeader mbap, word length)
void ModbusMakeHeader(struct ModbusApHeader mbap, word length, byte funcCode)
{
mbap.TxID = gTxID++; // [2] Transaction ID
mbap.Protocol = 0x0000; // [2] Protocol ID = 0 = Modbus
mbap.Length = length - __offset_of(struct ModbusApHeader, UnitID); // [2] Length; Number of bytes following
mbap.UnitID = 0xFF; // [1] Unit identifier; not relevant
mbap.FuncCode = funcCode; // [1] Function Code
}
// REGION: ModbusReadBits -------------------------------------------------------------
/// <ModbusReadBits>
void ModbusReadInBits(word address, word count)
void ModbusReadInBits(word address, long count)
{
ModbusReadBits(0x02, address, count);
}
/// <ModbusReadBits>
void ModbusReadBits(word address, word count)
void ModbusReadBits(word address, long count)
{
ModbusReadBits(0x02, address, count);
}
/// <ModbusReadBits>
void ModbusReadOutBits(word address, word count)
void ModbusReadOutBits(word address, long count)
{
ModbusReadBits(0x01, address, count);
}
/// <ModbusReadBits>
void ModbusReadBits(byte funcCode, word address, word count)
void ModbusReadBits(byte funcCode, word address, long count)
{
const byte length = __size_of(struct ModbusReqRead);
byte buffer[length];
struct ModbusReqRead mbr;
word curCount;
struct ModbusReqRead mbreq;
ModbusMakeHeader(mbr.Header, length);
// Payload
mbr.Header.FuncCode = funcCode; // [1] Function Code; 1: Read Coils (DI), 2: Read Discret Inputs (DIO), seems to be the same for WAGO 750-881
mbr.Address = address; // [2] Start address
mbr.Count = count; // [2] Number of items; 1:max 2000=0x7D0
// FC1: Read Coils (DO), FC2: Read Discret Inputs (DI)
while (count > 0)
{
curCount = count > gMaxBitsPerRead ? gMaxBitsPerRead : count;
ModbusMakeHeader(mbreq.Header, length, funcCode);
writeDbg(MbDebug, "Sending 'Read Bits' (0x01) command. Addr: 0x%04X, Count: %d", address, count);
memcpy_h2n(buffer, mbr);
ModbusSend(buffer, length, mbr.Header.TxID);
mbreq.Address = address; // [2] Start address
mbreq.Count = curCount; // [2] Number of items; 1:max 2000=0x7D0
writeDbg(MbDebug, "Sending 'Read Bits' (0x01) command. TxID: 0x%04X, Addr: 0x%04X, Count: %d", mbreq.Header.TxID, address, curCount);
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, length, mbreq.Header.TxID);
count -= gMaxBitsPerRead;
address += gMaxBitsPerRead;
}
}
/// <ModbusReadBits>
@ -98,7 +105,7 @@ void OnModbusReceiveBits(byte buffer[])
{
struct ModbusResReceiveBits mbres;
struct ModbusReqRead mbreq;
byte bitStatus[1968];
byte bitStatus[gMaxBitsPerRead];
word numBits;
byte i, j;
@ -106,7 +113,6 @@ void OnModbusReceiveBits(byte buffer[])
memcpy_n2h(mbreq, gQueueAck[mbres.Header.TxID].Buffer);
writeDbg(MbInfo, "Received %d bits from 0x%04X", mbreq.Count, mbreq.Address);
for (i = 0; i < mbres.ByteCount; i++)
{
for (j = 0; j < 8; j++)
@ -131,23 +137,45 @@ void OnModbusReceiveBitsException(struct ModbusApHeader mbap, enum ModbusExcepti
// REGION: ModbusReadRegisters -------------------------------------------------------
/// <ModbusReadRegisters>
void ModbusReadRegisters(word address, word count) // 16 bit
void ModbusReadInRegisters(word address, long count)
{
ModbusReadRegisters(0x04, address, count);
}
/// <ModbusReadRegisters>
void ModbusReadRegisters(word address, long count)
{
ModbusReadRegisters(0x04, address, count);
}
/// <ModbusReadRegisters>
void ModbusReadOutRegisters(word address, long count)
{
ModbusReadRegisters(0x03, address, count);
}
/// <ModbusReadRegisters>
void ModbusReadRegisters(byte funcCode, word address, long count)
{
const byte length = __size_of(struct ModbusReqRead);
const byte funcCode = 0x03;
byte buffer[length];
struct ModbusReqRead mbr;
ModbusMakeHeader(mbr.Header, length);
// Payload
mbr.Header.FuncCode = funcCode; // [1] Function Code; 3: Read Holding Registers (AI), 4: Read Input Registers (AIO), seems to be the same for WAGO 750-881
mbr.Address = address; // [2] Start address
mbr.Count = count; // [2] Number of items; 1:max 125=0x7D
word curCount;
struct ModbusReqRead mbreq;
writeDbg(MbDebug, "Sending 'Read Registers' (0x03) command. Addr: 0x%04X, Count: %d", address, count);
// FC3: Read Holding Registers (AO), FC4: Read Input Registers (AI)
while (count > 0)
{
curCount = count > gMaxRegsPerRead ? gMaxRegsPerRead : count;
ModbusMakeHeader(mbreq.Header, length, funcCode);
memcpy_h2n(buffer, mbr);
ModbusSend(buffer, length, mbr.Header.TxID);
mbreq.Address = address; // [2] Start address
mbreq.Count = curCount; // [2] Number of items; 1:max 125=0x7D
writeDbg(MbDebug, "Sending 'Read Registers' (0x03) command. TxID: 0x%04X, Addr: 0x%04X, Count: %d", mbreq.Header.TxID, address, curCount);
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, length, mbreq.Header.TxID);
count -= gMaxRegsPerRead;
address += gMaxRegsPerRead;
}
}
/// <ModbusReadRegisters>
@ -182,21 +210,21 @@ void ModbusWriteBit(word address, byte value)
const byte length = __size_of(struct ModbusReqWriteSingle);
const byte funcCode = 0x05; // B&R does not support 0x06
byte buffer[length];
struct ModbusReqWriteSingle mbw;
struct ModbusReqWriteSingle mbreq;
if (value >= 1)
value = 0xFF;
ModbusMakeHeader(mbw.Header, length);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 5: Write Single Coil (DO)
mbw.Address = address; // [2] Output address
mbw.Value = value << 8; // [2] Output value (0x0000: Off, 0xFF00: On)
// FC5: Write Single Coil (DO)
ModbusMakeHeader(mbreq.Header, length, funcCode);
writeDbg(Debug, "Sending 'Write Bit' (0x05) command. Addr: 0x%04X, Value: 0x%02X", address, value);
mbreq.Address = address; // [2] Output address
mbreq.Value = value << 8; // [2] Output value (0x0000: Off, 0xFF00: On)
writeDbg(Debug, "Sending 'Write Bit' (0x05) command. TxID: 0x%04X, Addr: 0x%04X, Value: %d", mbreq.Header.TxID, address, value);
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, length, mbw.Header.TxID);
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, length, mbreq.Header.TxID);
}
/// <ModbusWriteBit>
@ -224,23 +252,23 @@ void OnModbusConfirmBitException(struct ModbusApHeader mbap, enum ModbusExceptio
// REGION: ModbusWriteRegister ------------------------------------------------------
/// <ModbusWriteRegister>
void ModbusWriteRegister(word address, int value)
void ModbusWriteRegister(word address, word value)
{
const byte length = __size_of(struct ModbusReqWriteSingle);
const byte funcCode = 0x06;
byte buffer[length];
struct ModbusReqWriteSingle mbw;
struct ModbusReqWriteSingle mbreq;
ModbusMakeHeader(mbw.Header, length);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 5: Write Single Register (AO)
mbw.Address = address; // [2] Output address
mbw.Value = value; // [2] Output value
// 5: Write Single Register (AO)
ModbusMakeHeader(mbreq.Header, length, funcCode);
writeDbg(MbDebug, "Sending 'Write Register' (0x06) command. Addr: 0x%04X, Value: 0x%02X", address, value);
mbreq.Address = address; // [2] Output address
mbreq.Value = value; // [2] Output value
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, length, mbw.Header.TxID);
writeDbg(MbDebug, "Sending 'Write Register' (0x06) command. TxID: 0x%04X, Addr: 0x%04X, Value: %d", mbreq.Header.TxID, address, value);
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, length, mbreq.Header.TxID);
}
/// <ModbusWriteRegister>
@ -270,34 +298,42 @@ void OnModbusConfirmRegisterException(struct ModbusApHeader mbap, enum ModbusExc
// REGION: ModbusWriteBits ----------------------------------------------------------
/// <ModbusWriteBits>
void ModbusWriteBits(word address, word count, byte values[])
void ModbusWriteBits(word address, long count, byte values[])
{
const word maxLength = __size_of(struct ModbusReqWriteBits);
const byte funcCode = 0x0F;
byte buffer[maxLength];
struct ModbusReqWriteBits mbw;
struct ModbusReqWriteBits mbreq;
word curCount;
byte dataLength;
byte overallLength;
word i;
dataLength = _ceil(count / 8.0);
overallLength = maxLength - 1968/8 + dataLength;
ModbusMakeHeader(mbw.Header, overallLength);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 15: Write Multiple Bits (DOs)
mbw.Address = address; // [2] Output address
mbw.Count = count; // [2] Number of items; 1:max 1968=0x7B0
mbw.ByteCount = dataLength; // [1] Number of bytes; = ceil(count/8)
memcpy(mbw.Data, values, dataLength); // this is 1 memcpy too much -.-
// FC15: Write Multiple Bits (DOs)
while (count > 0)
{
curCount = count > gMaxBitsPerWrite ? gMaxBitsPerWrite : count;
dataLength = _ceil(curCount / 8.0);
overallLength = maxLength - gMaxBitsPerWrite/8 + dataLength;
ModbusMakeHeader(mbreq.Header, overallLength, funcCode);
writeDbg(MbDebug, "Sending 'Write Bits' (0x0F) command. Addr: 0x%04X, Count: %d", address, count);
mbreq.Address = address; // [2] Output address
mbreq.Count = curCount; // [2] Number of items; 1:max 1968=0x7B0
mbreq.ByteCount = dataLength; // [1] Number of bytes; = ceil(count/8)
memcpy(mbreq.Data, values, dataLength); // [246] Data; this is 1 unneccessary memcpy :( Well, readability...
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, overallLength, mbw.Header.TxID);
writeDbg(MbDebug, "Sending 'Write Bits' (0x0F) command. Addr: 0x%04X, Count: %d", address, curCount);
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, overallLength, mbreq.Header.TxID);
count -= gMaxBitsPerWrite;
address += gMaxBitsPerWrite;
}
}
/// <ModbusWriteBits>
void ModbusWriteBitsB(word address, word count, byte values[])
void ModbusWriteBitsB(word address, long count, byte values[])
{
byte buffer[2]; // length
word length;
@ -361,31 +397,38 @@ void OnModbusConfirmBitsException(struct ModbusApHeader mbap, enum ModbusExcepti
// REGION: ModbusWriteRegisters -------------------------------------------------------
/// <ModbusWriteRegisters>
void ModbusWriteRegisters(word address, word count, word values[])
void ModbusWriteRegisters(word address, long count, word values[])
{
const word maxLength = __size_of(struct ModbusReqWriteRegisters);
const byte funcCode = 0x10;
byte buffer[maxLength];
struct ModbusReqWriteRegisters mbw;
struct ModbusReqWriteRegisters mbreq;
word curCount;
byte dataLength;
word overallLength;
word i;
dataLength = 2 * count;
overallLength = maxLength - 2*123 + dataLength;
// FC16: Write Multiple Registers (AOs)
while (count > 0)
{
curCount = count > gMaxRegsPerWrite ? gMaxRegsPerWrite : count;
dataLength = 2 * curCount;
overallLength = maxLength - 2*gMaxRegsPerWrite + dataLength;
ModbusMakeHeader(mbw.Header, overallLength);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 16: Write Multiple Registers (AOs)
mbw.Address = address; // [2] Output address
mbw.Count = count; // [2] Number of items; 1:max 123=0x7B
mbw.ByteCount = dataLength; // [1] Number of bytes; = 2 * count
ModbusMakeHeader(mbreq.Header, overallLength, funcCode);
for (i = 0; i < dataLength; i++)
mbw.Data[i] = values[i];
mbreq.Address = address; // [2] Output address
mbreq.Count = curCount; // [2] Number of items; 1:max 123=0x7B
mbreq.ByteCount = dataLength; // [1] Number of bytes; = 2 * count
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, overallLength, mbw.Header.TxID);
for (i = 0; i < curCount; i++)
mbreq.Data[i] = values[i];
for ( ; i < gMaxRegsPerWrite; i++) // do we need this?
mbreq.Data[i] = 0;
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, overallLength, mbreq.Header.TxID);
}
}
/// <ModbusWriteRegisters>
@ -419,17 +462,17 @@ void ModbusWriteMasks(word address, word and, word or)
const word length = __size_of(struct ModbusReqWriteMasks);
const byte funcCode = 0x16;
byte buffer[length];
struct ModbusReqWriteMasks mbw;
struct ModbusReqWriteMasks mbreq;
ModbusMakeHeader(mbw.Header, length);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 22: Mask Write Registers (AO)
mbw.Address = address; // [2] Output address
mbw.And = and; // [2] AND mask
mbw.Or = or; // [2] OR mask
// FC22: Mask Write Registers (AO)
ModbusMakeHeader(mbreq.Header, length, funcCode);
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, length, mbw.Header.TxID);
mbreq.Address = address; // [2] Output address
mbreq.And = and; // [2] AND mask
mbreq.Or = or; // [2] OR mask
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, length, mbreq.Header.TxID);
}
/// <ModbusWriteMasks>
@ -457,33 +500,48 @@ void OnModbusConfirmMasksException(struct ModbusApHeader mbap, enum ModbusExcept
// REGION: ModbusReadWriteRegisters -------------------------------------------------------
/// <ModbusReadWriteRegisters>
void ModbusReadWriteRegisters(word readAddress, word readCount, word writeAddress, word writeCount, int values[])
void ModbusReadWriteRegisters(word readAddress, long readCount, word writeAddress, long writeCount, word values[])
{
const word maxLength = __size_of(struct ModbusReqReadWriteRegisters);
const byte funcCode = 0x17;
byte buffer[maxLength];
struct ModbusReqReadWriteRegisters mbw;
struct ModbusReqReadWriteRegisters mbreq;
byte dataLength;
word overallLength;
word i;
word i, offset;
offset = 0;
if (readCount > gMaxRegsPerRead - 2) // if we have to split the read request. count = n*max + y
{
ModbusReadRegisters(readAddress, readCount - readCount % gMaxRegsPerRead); // let this function read the main part: n*max
readAddress += readCount - readCount % gMaxRegsPerRead; // increment address by n*max
readCount %= gMaxRegsPerRead; // only read y elements in this function
}
if (writeCount > gMaxRegsPerWrite - 2) // if we have to split the write request. count = n*max + y
{
ModbusWriteRegisters(writeAddress, writeCount - writeCount % gMaxRegsPerWrite, values); // let this function read the main part: n*max
offset = writeCount - writeCount % gMaxRegsPerWrite; // start reading values at n*max
writeAddress += offset; // increment address by n*max
writeCount %= gMaxRegsPerWrite; // only read y elements in this function
}
dataLength = 2 * writeCount;
overallLength = maxLength - 2*121 + dataLength;
overallLength = maxLength - 2*(gMaxRegsPerWrite-2) + dataLength;
ModbusMakeHeader(mbw.Header, overallLength);
// Payload
mbw.Header.FuncCode = funcCode; // [1] Function Code; 16: Write Multiple Registers (AOs)
mbw.ReadAddress = readAddress; // [2] Input address
mbw.ReadCount = readCount; // [2] Number of items; 1:max 125=0x7D
mbw.WriteAddress = writeAddress;// [2] Output address
mbw.WriteCount = writeCount; // [2] Number of items; 1:max 121=0x79
mbw.ByteCount = dataLength; // [1] Number of bytes; = 2 * count
// FC16: Write Multiple Registers (AOs)
ModbusMakeHeader(mbreq.Header, overallLength, funcCode);
for (i = 0; i < dataLength; i++)
mbw.Data[i] = values[i];
mbreq.ReadAddress = readAddress; // [2] Input address
mbreq.ReadCount = readCount; // [2] Number of items; 1:max 125=0x7D
mbreq.WriteAddress = writeAddress; // [2] Output address
mbreq.WriteCount = writeCount; // [2] Number of items; 1:max 121=0x79
mbreq.ByteCount = dataLength; // [1] Number of bytes; = 2 * count
memcpy_h2n(buffer, mbw);
ModbusSend(buffer, overallLength, mbw.Header.TxID);
for (i = 0; i < writeCount; i++)
mbreq.Data[i] = values[i + offset];
memcpy_h2n(buffer, mbreq);
ModbusSend(buffer, overallLength, mbreq.Header.TxID);
}
/// <ModbusReadWriteRegisters>
@ -545,7 +603,7 @@ void OnModbusReceive(dword socket, long result, dword address, dword port, byte
void OnModbusReceive2(byte buffer[], dword size)
{
struct ModbusApHeader mbap;
int offset;
long offset;
char str[3*20];
if (size < 8) // No complete Modbus Application Header
@ -579,7 +637,7 @@ void OnModbusReceive2OnePacket(byte buffer[], int offset, struct ModbusApHeader
// Test unit/device identifier?
word i; // counter
word length; // length of current packet
byte mbuffer[__size_of(struct ModbusResReceiveRegisters)]; // second buffer where we copy the message. This way the user won't overwrite other packages.
byte mbuffer[gModbusMaxTelegramSize]; // second buffer where we copy the message. This way the user won't overwrite other packages.
length = __offset_of(struct ModbusApHeader, UnitID) + mbap.Length;
// We cannot check this properly anymore. We have to trust the TCP/UDP stack and the sender... *sigh*
@ -597,7 +655,7 @@ void OnModbusReceive2OnePacket(byte buffer[], int offset, struct ModbusApHeader
}
// MBAP Header is OK :) Go on
if (!gQueueSent.ContainsKey(mbap.TxID)) // We don't wait for this message!
if (!gQueueSent.ContainsKey(mbap.TxID)) // We don't wait for this message!?
return;
//write("Received TxID: %d", mbap.TxID);
@ -613,7 +671,6 @@ void OnModbusReceive2OnePacket(byte buffer[], int offset, struct ModbusApHeader
return;
}
// Copy the message
memcpy_off(mbuffer, 0, buffer, offset, length);
@ -780,8 +837,8 @@ on timer gtRobin
// Second: send new packets
for (long TxID : gQueuePending)
{
if (gQueueSent.Size() > 4) // Wago 750-881 cannot handle more than 5 messages at a time :(
continue;
if (gQueueSent.Size() >= gDevReceiveWindow) // Device cannot handle many messages at a time
break;
// if packet was sent or the socket is not currently being opened
if (ModbusSnd(gQueuePending[TxID].Buffer, gQueuePending[TxID].Length) == 0 || gSocketState != NULL)

View File

@ -1,72 +0,0 @@
/*@!Encoding:1252*/
variables
{
struct deviceIOs
{
byte InputRegisters;
word InputBits;
byte OutputRegisters;
word OutputBits;
char Modules[1024];
};
}
void ParseDeviceCode(word dev, enum Vendor vendor, struct deviceIOs dios)
{
byte input;
byte numChannels;
char module[10];
switch(vendor)
{
case Wago: // if this is a Wago device
if (dev & 0x8000) // Digital Module
{
numChannels = (dev >> 8) & 0x007F;
if (dev & 0x0001) // Input Module
{
input = 1;
strncpy(module, "DI%d,", elCount(module));
}
else if (dev & 0x0002) // Output Module
{
input = 0;
strncpy(module, "DO%d,", elCount(module));
}
else // blööd
{
writeDbg(AlgoError, "ParseDeviceCode: Device code 0x%X cannot be decoded", dev);
OnModbusClientPanics(DeviceCodeUnknown);
}
}
else
{
switch (dev)
{
case 881: // devices that have no inputs/outputs
return;
case 477: // devices that have 2 inputs
input = 1;
numChannels = 2;
break;
default: // unknown device. Ouch!
writeDbg(AlgoInfo, "Connected device: 750-%d", dev);
return;
}
if (input)
strncpy(module, "AI%d,", elCount(module));
else
strncpy(module, "AO%d,", elCount(module));
}
break; // switch(vendor)
default:
writeDbg(AlgoError, "ParseDeviceCode: Unknown vendor id: %d", vendor);
OnModbusClientPanics(VendorIdUnknown);
return;
}
snprintf(module, elCount(module), module, numChannels);
strncat(dios.Modules, module, elCount(dios.Modules));
}

View File

@ -1,6 +1,12 @@
/*@!Encoding:1252*/
variables
{
// according to Modbus Specification v1.1
const word gMaxBitsPerRead = 2000;
const word gMaxRegsPerRead = 125;
const word gMaxBitsPerWrite = 1968;
const word gMaxRegsPerWrite = 123;
// A normal Modbus Application Header. Every Modbus Packet begins with these 7 (+FuncCode) Bytes
_align(1) struct ModbusApHeader
{
@ -31,7 +37,7 @@ variables
word Address;
word Count;
byte ByteCount;
byte Data[246]; // Max length: 1968 bits
byte Data[gMaxBitsPerWrite/8]; // Max length: 1968 bits
};
// Write several values to bits starting with Address
_align(1) struct ModbusReqWriteRegisters
@ -40,7 +46,7 @@ variables
word Address;
word Count;
byte ByteCount;
word Data[123]; // Max length: 123 registers
word Data[gMaxRegsPerWrite]; // Max length: 123 registers
};
// Write AND and OR masks to a holding register
_align(1) struct ModbusReqWriteMasks
@ -59,7 +65,7 @@ variables
word WriteAddress;
word WriteCount;
byte ByteCount;
word Data[121]; // Max length: 123-2 registers
word Data[gMaxRegsPerWrite-2]; // Max length: 123-2 registers
};
@ -68,14 +74,14 @@ variables
{
struct ModbusApHeader Header;
byte ByteCount;
byte Data[250]; // Max length: 2000 bits
byte Data[gMaxBitsPerRead/8]; // Max length: 2000 bits
};
// Receive several register values
_align(1) struct ModbusResReceiveRegisters
{
struct ModbusApHeader Header;
byte ByteCount;
word Data[125]; // Max length: 125 registers
word Data[gMaxRegsPerRead]; // Max length: 125 registers
};
// Confirm the write of a single bit/register
_align(1) struct ModbusResConfirmSingle
@ -100,6 +106,7 @@ variables
word Or;
};
const word gModbusMaxTelegramSize = __size_of(struct ModbusResReceiveRegisters);
enum ModbusRequestError
@ -107,7 +114,7 @@ variables
Exception,
Timeout,
FinalTimeout
};
};
enum ModbusException
{
None = 0x00,
@ -143,9 +150,4 @@ variables
VendorIdUnknown = 0x03,
ConnectionError = 0x04
};
enum Vendor
{
Wago = 23,
BuR = 2
};
}

View File

@ -52,7 +52,7 @@ COLUMNWIDTHS=125,100,100,150,100,100,
HIDDEN=
ORDER=0,1,2,3,4,
DEFINITIONS=1,
COLUMNWIDTHS=125,125,100,150,100,
COLUMNWIDTHS=125,125,100,150,123,
[View_NetworkTxMessages]
HIDDEN=
ORDER=0,1,2,3,4,5,6,7,8,

View File

@ -1,16 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<systemvariables version="4">
<namespace name="" comment="">
<namespace name="Config" comment="">
<namespace name="Modbus" comment="">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="ms" name="RequestTimeout" comment="The maximum duration for a Modbus-UDP/-TCP request in milliseconds. After timeout a retransmission may be started (see MaxRetransmissionCount). Use `ping` to get the maximum latency to a device, double it and add 2-3 ms for processing." bitcount="32" isSigned="true" encoding="65001" type="int" startValue="7" minValue="1" minValuePhys="1" maxValue="1000" maxValuePhys="1000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Port" comment="" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="502" minValue="1" minValuePhys="1" maxValue="65535" maxValuePhys="65535" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="times" name="MaxTransmissionCount" comment="How often a retransmission of a request will be sent until it gets discarded and an error is thrown." bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="1" minValuePhys="1" maxValue="10" maxValuePhys="10" />
</namespace>
<namespace name="TcpIp" comment="">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="AdapterIndex" comment="Index of network interface to use" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="1" minValuePhys="1" maxValue="20" maxValuePhys="20" />
</namespace>
</namespace>
<namespace name="Ethernet1" comment="Subnet: 192.168.1.">
<namespace name="Client_2" comment="Server with ip address '192.168.1.2'">
<namespace name="Config" comment="Configuration section for this server">
@ -18,25 +8,25 @@
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="ms" name="Interval" comment="The interval with which the device will be queried" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="100" minValue="10" minValuePhys="10" maxValue="10000" maxValuePhys="10000" />
</namespace>
<namespace name="Info" comment="Some information about the device">
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="Vendor" comment="The vendor of the device" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="23">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Vendor" comment="The vendor of the device" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="23">
<valuetable definesMinMax="true">
<valuetableentry value="2" description="BuR" />
<valuetableentry value="23" description="Wago" />
</valuetable>
</variable>
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="SerialCode" comment="The serial code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="750" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="DeviceCode" comment="The device code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="881" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="Modules" comment="The type and number of inputs of modules that are connected to the server" bitcount="8" isSigned="true" encoding="65001" type="string" startValue="DI2,AI2,DO16" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="InputRegisters" comment="Number of input registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="0" minValuePhys="0" maxValue="123" maxValuePhys="123" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="InputBits" comment="Number of input bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="0" minValuePhys="0" maxValue="2000" maxValuePhys="2000" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="OutputRegisters" comment="Number of output registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="123" maxValuePhys="123" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="OutputBits" comment="Number of output bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="16" minValue="0" minValuePhys="0" maxValue="2000" maxValuePhys="2000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="Number of input registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="0" minValuePhys="0" maxValue="256" maxValuePhys="256" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Modules" comment="The type and number of inputs of modules that are connected to the server" bitcount="8" isSigned="true" encoding="65001" type="string" startValue="DI2,AI2,DO16" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="SerialCode" comment="The serial code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="750" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="DeviceCode" comment="The device code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="881" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="Number of input bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="0" minValuePhys="0" maxValue="256" maxValuePhys="256" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="Number of output bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="16" minValue="0" minValuePhys="0" maxValue="256" maxValuePhys="256" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="Number of output registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="256" maxValuePhys="256" />
</namespace>
<namespace name="Data" comment="The actual process image">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="The values of the input registers" bitcount="9" isSigned="true" encoding="65001" type="intarray" arrayLength="2" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="The state of the input bits" bitcount="2" isSigned="true" encoding="65001" type="intarray" arrayLength="2" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="The values of the output registers. Write here and the values will be sent to the device" bitcount="9" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="The state of the output bits. Write here and the values will be sent to the device" bitcount="2" isSigned="true" encoding="65001" type="intarray" arrayLength="16" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="The values of the input registers" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="2" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="The state of the input bits" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="2" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="The state of the output bits. Write here and the values will be sent to the device" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="16" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="The values of the output registers. Write here and the values will be sent to the device" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
</namespace>
</namespace>
<namespace name="Client_3" comment="Server with ip address '192.168.1.3'">
@ -45,27 +35,37 @@
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="ms" name="Interval" comment="The interval with which the device will be queried" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="100" minValue="10" minValuePhys="10" maxValue="10000" maxValuePhys="10000" />
</namespace>
<namespace name="Info" comment="Some information about the device">
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="Vendor" comment="The vendor of the device" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Vendor" comment="The vendor of the device" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2">
<valuetable definesMinMax="true">
<valuetableentry value="2" description="BuR" />
<valuetableentry value="23" description="Wago" />
</valuetable>
</variable>
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="SerialCode" comment="The serial code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="8828" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="DeviceCode" comment="The device code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="Modules" comment="The type and number of inputs of modules that are connected to the server" bitcount="8" isSigned="true" encoding="65001" type="string" startValue="" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="InputRegisters" comment="Number of input registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="4" minValue="0" minValuePhys="0" maxValue="2048" maxValuePhys="2048" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="InputBits" comment="Number of input bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="64" minValue="0" minValuePhys="0" maxValue="16384" maxValuePhys="16384" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="OutputRegisters" comment="Number of output registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="2048" maxValuePhys="2048" />
<variable anlyzLocal="2" readOnly="true" valueSequence="false" unit="" name="OutputBits" comment="Number of output bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="16384" maxValuePhys="16384" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="Number of input registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="7" minValue="0" minValuePhys="0" maxValue="2048" maxValuePhys="2048" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Modules" comment="The type and number of inputs of modules that are connected to the server" bitcount="8" isSigned="true" encoding="65001" type="string" startValue="" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="SerialCode" comment="The serial code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="8828" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="DeviceCode" comment="The device code of the server" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="1" minValuePhys="1" maxValue="10000" maxValuePhys="10000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="Number of input bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="16384" minValue="0" minValuePhys="0" maxValue="16384" maxValuePhys="16384" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="Number of output bits" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="16384" maxValuePhys="16384" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="Number of output registers" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="0" minValue="0" minValuePhys="0" maxValue="2048" maxValuePhys="2048" />
</namespace>
<namespace name="Data" comment="The actual process image">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="The values of the input registers" bitcount="9" isSigned="true" encoding="65001" type="intarray" arrayLength="4" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="The state of the input bits" bitcount="2" isSigned="true" encoding="65001" type="intarray" arrayLength="64" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="The values of the output registers. Write here and the values will be sent to the device" bitcount="9" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="The state of the output bits. Write here and the values will be sent to the device" bitcount="2" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputRegisters" comment="The values of the input registers" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="7" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="InputBits" comment="The state of the input bits" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="16384" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputBits" comment="The state of the output bits. Write here and the values will be sent to the device" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="OutputRegisters" comment="The values of the output registers. Write here and the values will be sent to the device" bitcount="32" isSigned="true" encoding="65001" type="intarray" arrayLength="0" />
</namespace>
</namespace>
</namespace>
<namespace name="Config" comment="">
<namespace name="Modbus" comment="">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="ms" name="RequestTimeout" comment="The maximum duration for a Modbus-UDP/-TCP request in milliseconds. After timeout a retransmission may be started (see MaxRetransmissionCount). Use `ping` to get the maximum latency to a device, double it and add 2-3 ms for processing." bitcount="32" isSigned="true" encoding="65001" type="int" startValue="80" minValue="1" minValuePhys="1" maxValue="1000" maxValuePhys="1000" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="Port" comment="" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="502" minValue="1" minValuePhys="1" maxValue="65535" maxValuePhys="65535" />
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="times" name="MaxTransmissionCount" comment="How often a retransmission of a request will be sent until it gets discarded and an error is thrown." bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="1" minValuePhys="1" maxValue="10" maxValuePhys="10" />
</namespace>
<namespace name="TcpIp" comment="">
<variable anlyzLocal="2" readOnly="false" valueSequence="false" unit="" name="AdapterIndex" comment="Index of network interface to use" bitcount="32" isSigned="true" encoding="65001" type="int" startValue="2" minValue="1" minValuePhys="1" maxValue="20" maxValuePhys="20" />
</namespace>
</namespace>
</namespace>
</systemvariables>
</systemvariables>