|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # This file is part of Beremiz, a Integrated Development Environment for |
|
5 # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. |
|
6 # |
|
7 # Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) |
|
8 # |
|
9 # This program is free software: you can redistribute it and/or modify |
|
10 # it under the terms of the GNU General Public License as published by |
|
11 # the Free Software Foundation, either version 3 of the License, or |
|
12 # (at your option) any later version. |
|
13 # |
|
14 # This program is distributed in the hope that it will be useful, |
|
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
17 # GNU General Public License for more details. |
|
18 # |
|
19 # You should have received a copy of the GNU General Public License |
|
20 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
21 # |
|
22 # This code is made available on the understanding that it will not be |
|
23 # used in safety-critical situations without a full and competent review. |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 import os, sys |
|
29 base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] |
|
30 base_folder = os.path.join(base_folder, "..") |
|
31 ModbusPath = os.path.join(base_folder, "Modbus") |
|
32 |
|
33 from mb_utils import * |
|
34 |
|
35 import wx |
|
36 from ConfigTreeNode import ConfigTreeNode |
|
37 from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY |
|
38 |
|
39 |
|
40 |
|
41 ################################################### |
|
42 ################################################### |
|
43 # # |
|
44 # C L I E N T R E Q U E S T # |
|
45 # # |
|
46 ################################################### |
|
47 ################################################### |
|
48 |
|
49 |
|
50 class _RequestPlug: |
|
51 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
52 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
53 <xsd:element name="ModbusRequest"> |
|
54 <xsd:complexType> |
|
55 <xsd:attribute name="Function" type="xsd:string" use="optional" default="01 - Read Coils"/> |
|
56 <xsd:attribute name="SlaveID" use="optional" default="1"> |
|
57 <xsd:simpleType> |
|
58 <xsd:restriction base="xsd:integer"> |
|
59 <xsd:minInclusive value="0"/> |
|
60 <xsd:maxInclusive value="255"/> |
|
61 </xsd:restriction> |
|
62 </xsd:simpleType> |
|
63 </xsd:attribute> |
|
64 <xsd:attribute name="Nr_of_Channels" use="optional" default="1"> |
|
65 <xsd:simpleType> |
|
66 <xsd:restriction base="xsd:integer"> |
|
67 <xsd:minInclusive value="1"/> |
|
68 <xsd:maxInclusive value="2000"/> |
|
69 </xsd:restriction> |
|
70 </xsd:simpleType> |
|
71 </xsd:attribute> |
|
72 <xsd:attribute name="Start_Address" use="optional" default="0"> |
|
73 <xsd:simpleType> |
|
74 <xsd:restriction base="xsd:integer"> |
|
75 <xsd:minInclusive value="0"/> |
|
76 <xsd:maxInclusive value="65535"/> |
|
77 </xsd:restriction> |
|
78 </xsd:simpleType> |
|
79 </xsd:attribute> |
|
80 <xsd:attribute name="Timeout_in_ms" use="optional" default="10"> |
|
81 <xsd:simpleType> |
|
82 <xsd:restriction base="xsd:integer"> |
|
83 <xsd:minInclusive value="1"/> |
|
84 <xsd:maxInclusive value="100000"/> |
|
85 </xsd:restriction> |
|
86 </xsd:simpleType> |
|
87 </xsd:attribute> |
|
88 </xsd:complexType> |
|
89 </xsd:element> |
|
90 </xsd:schema> |
|
91 """ |
|
92 |
|
93 def GetParamsAttributes(self, path = None): |
|
94 infos = ConfigTreeNode.GetParamsAttributes(self, path = path) |
|
95 for element in infos: |
|
96 if element["name"] == "ModbusRequest": |
|
97 for child in element["children"]: |
|
98 if child["name"] == "Function": |
|
99 list = modbus_function_dict.keys() |
|
100 list.sort() |
|
101 child["type"] = list |
|
102 return infos |
|
103 |
|
104 def GetVariableLocationTree(self): |
|
105 current_location = self.GetCurrentLocation() |
|
106 name = self.BaseParams.getName() |
|
107 address = self.GetParamsAttributes()[0]["children"][3]["value"] |
|
108 count = self.GetParamsAttributes()[0]["children"][2]["value"] |
|
109 function= self.GetParamsAttributes()[0]["children"][0]["value"] |
|
110 # 'BOOL' or 'WORD' |
|
111 datatype= modbus_function_dict[function][3] |
|
112 # 1 or 16 |
|
113 datasize= modbus_function_dict[function][4] |
|
114 # 'Q' for coils and holding registers, 'I' for input discretes and input registers |
|
115 datazone= modbus_function_dict[function][5] |
|
116 # 'X' for bits, 'W' for words |
|
117 datatacc= modbus_function_dict[function][6] |
|
118 # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' |
|
119 dataname= modbus_function_dict[function][7] |
|
120 entries = [] |
|
121 for offset in range(address, address+count): |
|
122 entries.append({ |
|
123 "name": dataname + " " + str(offset), |
|
124 "type": LOCATION_VAR_MEMORY, |
|
125 "size": datasize, |
|
126 "IEC_type": datatype, |
|
127 "var_name": "var_name", |
|
128 "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), |
|
129 "description": "description", |
|
130 "children": []}) |
|
131 return {"name": name, |
|
132 "type": LOCATION_CONFNODE, |
|
133 "location": ".".join([str(i) for i in current_location]) + ".x", |
|
134 "children": entries} |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 def CTNGenerate_C(self, buildpath, locations): |
|
141 """ |
|
142 Generate C code |
|
143 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
144 @param locations: List of complete variables locations \ |
|
145 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
146 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
147 "DIR" : direction "Q","I" or "M" |
|
148 "SIZE" : size "X", "B", "W", "D", "L" |
|
149 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
150 }, ...] |
|
151 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
152 """ |
|
153 return [], "", False |
|
154 |
|
155 |
|
156 ################################################### |
|
157 ################################################### |
|
158 # # |
|
159 # S E R V E R M E M O R Y A R E A # |
|
160 # # |
|
161 ################################################### |
|
162 ################################################### |
|
163 |
|
164 #dictionary implementing: |
|
165 #key - string with the description we want in the request plugin GUI |
|
166 #list - (modbus function number, request type, max count value) |
|
167 modbus_memtype_dict = {"01 - Coils" : ( '1', 'rw_bits', 65536, "BOOL", 1 , "Q", "X", "Coil"), |
|
168 "02 - Input Discretes" : ( '2', 'ro_bits', 65536, "BOOL", 1 , "I", "X", "Input Discrete"), |
|
169 "03 - Holding Registers" :( '3', 'rw_words', 65536, "WORD", 16 , "Q", "W", "Holding Register"), |
|
170 "04 - Input Registers" : ( '4', 'ro_words', 65536, "WORD", 16 , "I", "W", "Input Register"), |
|
171 } |
|
172 |
|
173 class _MemoryAreaPlug: |
|
174 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
175 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
176 <xsd:element name="MemoryArea"> |
|
177 <xsd:complexType> |
|
178 <xsd:attribute name="MemoryAreaType" type="xsd:string" use="optional" default="01 - Coils"/> |
|
179 <xsd:attribute name="Nr_of_Channels" use="optional" default="1"> |
|
180 <xsd:simpleType> |
|
181 <xsd:restriction base="xsd:integer"> |
|
182 <xsd:minInclusive value="1"/> |
|
183 <xsd:maxInclusive value="65536"/> |
|
184 </xsd:restriction> |
|
185 </xsd:simpleType> |
|
186 </xsd:attribute> |
|
187 <xsd:attribute name="Start_Address" use="optional" default="0"> |
|
188 <xsd:simpleType> |
|
189 <xsd:restriction base="xsd:integer"> |
|
190 <xsd:minInclusive value="0"/> |
|
191 <xsd:maxInclusive value="65535"/> |
|
192 </xsd:restriction> |
|
193 </xsd:simpleType> |
|
194 </xsd:attribute> |
|
195 </xsd:complexType> |
|
196 </xsd:element> |
|
197 </xsd:schema> |
|
198 """ |
|
199 |
|
200 def GetParamsAttributes(self, path = None): |
|
201 infos = ConfigTreeNode.GetParamsAttributes(self, path = path) |
|
202 for element in infos: |
|
203 if element["name"] == "MemoryArea": |
|
204 for child in element["children"]: |
|
205 if child["name"] == "MemoryAreaType": |
|
206 list = modbus_memtype_dict.keys() |
|
207 list.sort() |
|
208 child["type"] = list |
|
209 return infos |
|
210 |
|
211 def GetVariableLocationTree(self): |
|
212 current_location = self.GetCurrentLocation() |
|
213 name = self.BaseParams.getName() |
|
214 address = self.GetParamsAttributes()[0]["children"][2]["value"] |
|
215 count = self.GetParamsAttributes()[0]["children"][1]["value"] |
|
216 function= self.GetParamsAttributes()[0]["children"][0]["value"] |
|
217 # 'BOOL' or 'WORD' |
|
218 datatype= modbus_memtype_dict[function][3] |
|
219 # 1 or 16 |
|
220 datasize= modbus_memtype_dict[function][4] |
|
221 # 'Q' for coils and holding registers, 'I' for input discretes and input registers |
|
222 datazone= modbus_memtype_dict[function][5] |
|
223 # 'X' for bits, 'W' for words |
|
224 datatacc= modbus_memtype_dict[function][6] |
|
225 # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' |
|
226 dataname= modbus_memtype_dict[function][7] |
|
227 entries = [] |
|
228 for offset in range(address, address+count): |
|
229 entries.append({ |
|
230 "name": dataname + " " + str(offset), |
|
231 "type": LOCATION_VAR_MEMORY, |
|
232 "size": datasize, |
|
233 "IEC_type": datatype, |
|
234 "var_name": "var_name", |
|
235 "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), |
|
236 "description": "description", |
|
237 "children": []}) |
|
238 return {"name": name, |
|
239 "type": LOCATION_CONFNODE, |
|
240 "location": ".".join([str(i) for i in current_location]) + ".x", |
|
241 "children": entries} |
|
242 |
|
243 def CTNGenerate_C(self, buildpath, locations): |
|
244 """ |
|
245 Generate C code |
|
246 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
247 @param locations: List of complete variables locations \ |
|
248 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
249 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
250 "DIR" : direction "Q","I" or "M" |
|
251 "SIZE" : size "X", "B", "W", "D", "L" |
|
252 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
253 }, ...] |
|
254 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
255 """ |
|
256 return [], "", False |
|
257 |
|
258 |
|
259 ################################################### |
|
260 ################################################### |
|
261 # # |
|
262 # T C P C L I E N T # |
|
263 # # |
|
264 ################################################### |
|
265 ################################################### |
|
266 |
|
267 class _ModbusTCPclientPlug: |
|
268 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
269 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
270 <xsd:element name="ModbusTCPclient"> |
|
271 <xsd:complexType> |
|
272 <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/> |
|
273 <xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/> |
|
274 <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100"> |
|
275 <xsd:simpleType> |
|
276 <xsd:restriction base="xsd:unsignedLong"> |
|
277 <xsd:minInclusive value="1"/> |
|
278 <xsd:maxInclusive value="2147483647"/> |
|
279 </xsd:restriction> |
|
280 </xsd:simpleType> |
|
281 </xsd:attribute> |
|
282 </xsd:complexType> |
|
283 </xsd:element> |
|
284 </xsd:schema> |
|
285 """ |
|
286 # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms corresponds to aprox 25 days. |
|
287 CTNChildrenTypes = [("ModbusRequest",_RequestPlug, "Request")] |
|
288 # TODO: Replace with CTNType !!! |
|
289 PlugType = "ModbusTCPclient" |
|
290 |
|
291 # Return the number of (modbus library) nodes this specific TCP client will need |
|
292 # return type: (tcp nodes, rtu nodes, ascii nodes) |
|
293 def GetNodeCount(self): |
|
294 return (1, 0, 0) |
|
295 |
|
296 def CTNGenerate_C(self, buildpath, locations): |
|
297 """ |
|
298 Generate C code |
|
299 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
300 @param locations: List of complete variables locations \ |
|
301 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
302 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
303 "DIR" : direction "Q","I" or "M" |
|
304 "SIZE" : size "X", "B", "W", "D", "L" |
|
305 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
306 }, ...] |
|
307 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
308 """ |
|
309 return [], "", False |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 ################################################### |
|
315 ################################################### |
|
316 # # |
|
317 # T C P S E R V E R # |
|
318 # # |
|
319 ################################################### |
|
320 ################################################### |
|
321 |
|
322 class _ModbusTCPserverPlug: |
|
323 # NOTE: the Port number is a 'string' and not an 'integer'! |
|
324 # This is because the underlying modbus library accepts strings |
|
325 # (e.g.: well known port names!) |
|
326 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
327 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
328 <xsd:element name="ModbusServerNode"> |
|
329 <xsd:complexType> |
|
330 <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional" default="#ANY#"/> |
|
331 <xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/> |
|
332 <xsd:attribute name="SlaveID" use="optional" default="0"> |
|
333 <xsd:simpleType> |
|
334 <xsd:restriction base="xsd:integer"> |
|
335 <xsd:minInclusive value="0"/> |
|
336 <xsd:maxInclusive value="255"/> |
|
337 </xsd:restriction> |
|
338 </xsd:simpleType> |
|
339 </xsd:attribute> |
|
340 </xsd:complexType> |
|
341 </xsd:element> |
|
342 </xsd:schema> |
|
343 """ |
|
344 CTNChildrenTypes = [("MemoryArea",_MemoryAreaPlug, "Memory Area")] |
|
345 # TODO: Replace with CTNType !!! |
|
346 PlugType = "ModbusTCPserver" |
|
347 |
|
348 # Return the number of (modbus library) nodes this specific TCP server will need |
|
349 # return type: (tcp nodes, rtu nodes, ascii nodes) |
|
350 def GetNodeCount(self): |
|
351 return (1, 0, 0) |
|
352 |
|
353 # Return a list with a single tuple conatining the (location, port number) |
|
354 # location: location of this node in the configuration tree |
|
355 # port number: IP port used by this Modbus/IP server |
|
356 def GetIPServerPortNumbers(self): |
|
357 port = self.GetParamsAttributes()[0]["children"][1]["value"] |
|
358 return [(self.GetCurrentLocation() , port)] |
|
359 |
|
360 def CTNGenerate_C(self, buildpath, locations): |
|
361 """ |
|
362 Generate C code |
|
363 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
364 @param locations: List of complete variables locations \ |
|
365 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
366 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
367 "DIR" : direction "Q","I" or "M" |
|
368 "SIZE" : size "X", "B", "W", "D", "L" |
|
369 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
370 }, ...] |
|
371 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
372 """ |
|
373 return [], "", False |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 ################################################### |
|
379 ################################################### |
|
380 # # |
|
381 # R T U C L I E N T # |
|
382 # # |
|
383 ################################################### |
|
384 ################################################### |
|
385 |
|
386 class _ModbusRTUclientPlug: |
|
387 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
388 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
389 <xsd:element name="ModbusRTUclient"> |
|
390 <xsd:complexType> |
|
391 <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/> |
|
392 <xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/> |
|
393 <xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/> |
|
394 <xsd:attribute name="Stop_Bits" type="xsd:string" use="optional" default="1"/> |
|
395 <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100"> |
|
396 <xsd:simpleType> |
|
397 <xsd:restriction base="xsd:integer"> |
|
398 <xsd:minInclusive value="1"/> |
|
399 <xsd:maxInclusive value="2147483647"/> |
|
400 </xsd:restriction> |
|
401 </xsd:simpleType> |
|
402 </xsd:attribute> |
|
403 </xsd:complexType> |
|
404 </xsd:element> |
|
405 </xsd:schema> |
|
406 """ |
|
407 # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms corresponds to aprox 25 days. |
|
408 CTNChildrenTypes = [("ModbusRequest",_RequestPlug, "Request")] |
|
409 # TODO: Replace with CTNType !!! |
|
410 PlugType = "ModbusRTUclient" |
|
411 |
|
412 def GetParamsAttributes(self, path = None): |
|
413 infos = ConfigTreeNode.GetParamsAttributes(self, path = path) |
|
414 for element in infos: |
|
415 if element["name"] == "ModbusRTUclient": |
|
416 for child in element["children"]: |
|
417 if child["name"] == "Baud_Rate": |
|
418 child["type"] = modbus_serial_baudrate_list |
|
419 if child["name"] == "Stop_Bits": |
|
420 child["type"] = modbus_serial_stopbits_list |
|
421 if child["name"] == "Parity": |
|
422 child["type"] = modbus_serial_parity_dict.keys() |
|
423 return infos |
|
424 |
|
425 # Return the number of (modbus library) nodes this specific RTU client will need |
|
426 # return type: (tcp nodes, rtu nodes, ascii nodes) |
|
427 def GetNodeCount(self): |
|
428 return (0, 1, 0) |
|
429 |
|
430 def CTNGenerate_C(self, buildpath, locations): |
|
431 """ |
|
432 Generate C code |
|
433 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
434 @param locations: List of complete variables locations \ |
|
435 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
436 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
437 "DIR" : direction "Q","I" or "M" |
|
438 "SIZE" : size "X", "B", "W", "D", "L" |
|
439 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
440 }, ...] |
|
441 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
442 """ |
|
443 return [], "", False |
|
444 |
|
445 |
|
446 |
|
447 ################################################### |
|
448 ################################################### |
|
449 # # |
|
450 # R T U S L A V E # |
|
451 # # |
|
452 ################################################### |
|
453 ################################################### |
|
454 |
|
455 |
|
456 class _ModbusRTUslavePlug: |
|
457 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
458 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
459 <xsd:element name="ModbusRTUslave"> |
|
460 <xsd:complexType> |
|
461 <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/> |
|
462 <xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/> |
|
463 <xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/> |
|
464 <xsd:attribute name="Stop_Bits" type="xsd:string" use="optional" default="1"/> |
|
465 <xsd:attribute name="SlaveID" use="optional" default="1"> |
|
466 <xsd:simpleType> |
|
467 <xsd:restriction base="xsd:integer"> |
|
468 <xsd:minInclusive value="1"/> |
|
469 <xsd:maxInclusive value="255"/> |
|
470 </xsd:restriction> |
|
471 </xsd:simpleType> |
|
472 </xsd:attribute> |
|
473 </xsd:complexType> |
|
474 </xsd:element> |
|
475 </xsd:schema> |
|
476 """ |
|
477 CTNChildrenTypes = [("MemoryArea",_MemoryAreaPlug, "Memory Area")] |
|
478 # TODO: Replace with CTNType !!! |
|
479 PlugType = "ModbusRTUslave" |
|
480 |
|
481 def GetParamsAttributes(self, path = None): |
|
482 infos = ConfigTreeNode.GetParamsAttributes(self, path = path) |
|
483 for element in infos: |
|
484 if element["name"] == "ModbusRTUslave": |
|
485 for child in element["children"]: |
|
486 if child["name"] == "Baud_Rate": |
|
487 child["type"] = modbus_serial_baudrate_list |
|
488 if child["name"] == "Stop_Bits": |
|
489 child["type"] = modbus_serial_stopbits_list |
|
490 if child["name"] == "Parity": |
|
491 child["type"] = modbus_serial_parity_dict.keys() |
|
492 return infos |
|
493 |
|
494 # Return the number of (modbus library) nodes this specific RTU slave will need |
|
495 # return type: (tcp nodes, rtu nodes, ascii nodes) |
|
496 def GetNodeCount(self): |
|
497 return (0, 1, 0) |
|
498 |
|
499 def CTNGenerate_C(self, buildpath, locations): |
|
500 """ |
|
501 Generate C code |
|
502 @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) |
|
503 @param locations: List of complete variables locations \ |
|
504 [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) |
|
505 "NAME" : name of the variable (generally "__IW0_1_2" style) |
|
506 "DIR" : direction "Q","I" or "M" |
|
507 "SIZE" : size "X", "B", "W", "D", "L" |
|
508 "LOC" : tuple of interger for IEC location (0,1,2,...) |
|
509 }, ...] |
|
510 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
|
511 """ |
|
512 return [], "", False |
|
513 |
|
514 |
|
515 |
|
516 ################################################### |
|
517 ################################################### |
|
518 # # |
|
519 # R O O T C L A S S # |
|
520 # # |
|
521 ################################################### |
|
522 ################################################### |
|
523 class RootClass: |
|
524 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
|
525 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
526 <xsd:element name="ModbusRoot"> |
|
527 <xsd:complexType> |
|
528 <xsd:attribute name="MaxRemoteTCPclients" use="optional" default="10"> |
|
529 <xsd:simpleType> |
|
530 <xsd:restriction base="xsd:integer"> |
|
531 <xsd:minInclusive value="0"/> |
|
532 <xsd:maxInclusive value="65535"/> |
|
533 </xsd:restriction> |
|
534 </xsd:simpleType> |
|
535 </xsd:attribute> |
|
536 </xsd:complexType> |
|
537 </xsd:element> |
|
538 </xsd:schema> |
|
539 """ |
|
540 CTNChildrenTypes = [("ModbusTCPclient",_ModbusTCPclientPlug, "Modbus TCP Client") |
|
541 ,("ModbusTCPserver",_ModbusTCPserverPlug, "Modbus TCP Server") |
|
542 ,("ModbusRTUclient",_ModbusRTUclientPlug, "Modbus RTU Client") |
|
543 ,("ModbusRTUslave", _ModbusRTUslavePlug, "Modbus RTU Slave") |
|
544 ] |
|
545 |
|
546 # Return the number of (modbus library) nodes this specific instance of the modbus plugin will need |
|
547 # return type: (tcp nodes, rtu nodes, ascii nodes) |
|
548 def GetNodeCount(self): |
|
549 max_remote_tcpclient = self.GetParamsAttributes()[0]["children"][0]["value"] |
|
550 total_node_count = (max_remote_tcpclient, 0, 0) |
|
551 for child in self.IECSortedChildren(): |
|
552 # ask each child how many nodes it needs, and add them all up. |
|
553 total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount())) |
|
554 return total_node_count |
|
555 |
|
556 # Return a list with tuples of the (location, port numbers) used by all the Modbus/IP servers |
|
557 def GetIPServerPortNumbers(self): |
|
558 IPServer_port_numbers = [] |
|
559 for child in self.IECSortedChildren(): |
|
560 if child.CTNType == "ModbusTCPserver": |
|
561 IPServer_port_numbers.extend(child.GetIPServerPortNumbers()) |
|
562 return IPServer_port_numbers |
|
563 |
|
564 def CTNGenerate_C(self, buildpath, locations): |
|
565 #print "#############" |
|
566 #print self.__class__ |
|
567 #print type(self) |
|
568 #print "self.CTNType >>>" |
|
569 #print self.CTNType |
|
570 #print "type(self.CTNType) >>>" |
|
571 #print type(self.CTNType) |
|
572 #print "#############" |
|
573 |
|
574 loc_dict = {"locstr" : "_".join(map(str,self.GetCurrentLocation())), |
|
575 } |
|
576 |
|
577 # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need |
|
578 # total_node_count: (tcp nodes, rtu nodes, ascii nodes) |
|
579 # Also get a list with tuples of (location, IP port numbers) used by all the Modbus/IP server nodes |
|
580 # This list is later used to search for duplicates in port numbers! |
|
581 # IPServer_port_numbers = [(location ,IPserver_port_number), ...] |
|
582 # location: tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x" |
|
583 # IPserver_port_number: a number (i.e. port number used by the Modbus/IP server) |
|
584 total_node_count = (0, 0, 0) |
|
585 IPServer_port_numbers = [] |
|
586 for CTNInstance in self.GetCTRoot().IterChildren(): |
|
587 if CTNInstance.CTNType == "modbus": |
|
588 # ask each modbus plugin instance how many nodes it needs, and add them all up. |
|
589 total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, CTNInstance.GetNodeCount())) |
|
590 IPServer_port_numbers.extend(CTNInstance.GetIPServerPortNumbers()) |
|
591 |
|
592 # Search for use of duplicate port numbers by Modbus/IP servers |
|
593 #print IPServer_port_numbers |
|
594 # ..but first define a lambda function to convert a tuple with the config tree location to a nice looking string |
|
595 # for e.g., convert the tuple (0, 3, 4) to "0.3.4" |
|
596 lt_to_str = lambda loctuple: '.'.join(map(str, loctuple)) |
|
597 for i in range(0, len(IPServer_port_numbers)-1): |
|
598 for j in range (i+1, len(IPServer_port_numbers)): |
|
599 if IPServer_port_numbers[i][1] == IPServer_port_numbers[j][1]: |
|
600 self.GetCTRoot().logger.write_warning(_("Error: Modbus/IP Servers %s.x and %s.x use the same port number %s.\n")%(lt_to_str(IPServer_port_numbers[i][0]), lt_to_str(IPServer_port_numbers[j][0]), IPServer_port_numbers[j][1])) |
|
601 raise Exception, False |
|
602 # TODO: return an error code instead of raising an exception |
|
603 |
|
604 # Determine the current location in Beremiz's project configuration tree |
|
605 current_location = self.GetCurrentLocation() |
|
606 |
|
607 # define a unique name for the generated C and h files |
|
608 prefix = "_".join(map(str, current_location)) |
|
609 Gen_MB_c_path = os.path.join(buildpath, "MB_%s.c"%prefix) |
|
610 Gen_MB_h_path = os.path.join(buildpath, "MB_%s.h"%prefix) |
|
611 c_filename = os.path.join(os.path.split(__file__)[0],"mb_runtime.c") |
|
612 h_filename = os.path.join(os.path.split(__file__)[0],"mb_runtime.h") |
|
613 |
|
614 tcpclient_reqs_count = 0 |
|
615 rtuclient_reqs_count = 0 |
|
616 ascclient_reqs_count = 0 |
|
617 tcpclient_node_count = 0 |
|
618 rtuclient_node_count = 0 |
|
619 ascclient_node_count = 0 |
|
620 tcpserver_node_count = 0 |
|
621 rtuserver_node_count = 0 |
|
622 ascserver_node_count = 0 |
|
623 nodeid = 0 |
|
624 client_nodeid = 0 |
|
625 client_requestid = 0 |
|
626 server_id = 0 |
|
627 |
|
628 server_node_list = [] |
|
629 client_node_list = [] |
|
630 client_request_list = [] |
|
631 server_memarea_list = [] |
|
632 loc_vars = [] |
|
633 loc_vars_list = [] # list of variables already declared in C code! |
|
634 for child in self.IECSortedChildren(): |
|
635 #print "<<<<<<<<<<<<<" |
|
636 #print "child (self.IECSortedChildren())----->" |
|
637 #print child.__class__ |
|
638 #print ">>>>>>>>>>>>>" |
|
639 ###################################### |
|
640 if child.PlugType == "ModbusTCPserver": |
|
641 tcpserver_node_count += 1 |
|
642 new_node = GetTCPServerNodePrinted(self, child) |
|
643 if new_node is None: |
|
644 return [],"",False |
|
645 server_node_list.append(new_node) |
|
646 ############## |
|
647 for subchild in child.IECSortedChildren(): |
|
648 new_memarea = GetTCPServerMemAreaPrinted(self, subchild, nodeid) |
|
649 if new_memarea is None: |
|
650 return [],"",False |
|
651 server_memarea_list.append(new_memarea) |
|
652 function= subchild.GetParamsAttributes()[0]["children"][0]["value"] |
|
653 # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' |
|
654 memarea= modbus_memtype_dict[function][1] |
|
655 for iecvar in subchild.GetLocations(): |
|
656 #print repr(iecvar) |
|
657 absloute_address = iecvar["LOC"][3] |
|
658 start_address = int(subchild.GetParamsAttributes()[0]["children"][2]["value"]) |
|
659 relative_addr = absloute_address - start_address |
|
660 #test if relative address in request specified range |
|
661 if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][1]["value"])): |
|
662 if str(iecvar["NAME"]) not in loc_vars_list: |
|
663 loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (server_id, memarea, absloute_address)) |
|
664 loc_vars_list.append(str(iecvar["NAME"])) |
|
665 server_id += 1 |
|
666 ###################################### |
|
667 if child.PlugType == "ModbusRTUslave": |
|
668 rtuserver_node_count += 1 |
|
669 new_node = GetRTUSlaveNodePrinted(self, child) |
|
670 if new_node is None: |
|
671 return [],"",False |
|
672 server_node_list.append(new_node) |
|
673 ############## |
|
674 for subchild in child.IECSortedChildren(): |
|
675 new_memarea = GetTCPServerMemAreaPrinted(self, subchild, nodeid) |
|
676 if new_memarea is None: |
|
677 return [],"",False |
|
678 server_memarea_list.append(new_memarea) |
|
679 function= subchild.GetParamsAttributes()[0]["children"][0]["value"] |
|
680 # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' |
|
681 memarea= modbus_memtype_dict[function][1] |
|
682 for iecvar in subchild.GetLocations(): |
|
683 #print repr(iecvar) |
|
684 absloute_address = iecvar["LOC"][3] |
|
685 start_address = int(subchild.GetParamsAttributes()[0]["children"][2]["value"]) |
|
686 relative_addr = absloute_address - start_address |
|
687 #test if relative address in request specified range |
|
688 if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][1]["value"])): |
|
689 if str(iecvar["NAME"]) not in loc_vars_list: |
|
690 loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (server_id, memarea, absloute_address)) |
|
691 loc_vars_list.append(str(iecvar["NAME"])) |
|
692 server_id += 1 |
|
693 ###################################### |
|
694 if child.PlugType == "ModbusTCPclient": |
|
695 tcpclient_reqs_count += len(child.IECSortedChildren()) |
|
696 new_node = GetTCPClientNodePrinted(self, child) |
|
697 if new_node is None: |
|
698 return [],"",False |
|
699 client_node_list.append(new_node) |
|
700 for subchild in child.IECSortedChildren(): |
|
701 new_req = GetClientRequestPrinted(self, subchild, client_nodeid) |
|
702 if new_req is None: |
|
703 return [],"",False |
|
704 client_request_list.append(new_req) |
|
705 for iecvar in subchild.GetLocations(): |
|
706 #absloute address - start address |
|
707 relative_addr = iecvar["LOC"][3] - int(subchild.GetParamsAttributes()[0]["children"][3]["value"]) |
|
708 #test if relative address in request specified range |
|
709 if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][2]["value"])): |
|
710 if str(iecvar["NAME"]) not in loc_vars_list: |
|
711 loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) |
|
712 loc_vars_list.append(str(iecvar["NAME"])) |
|
713 client_requestid += 1 |
|
714 tcpclient_node_count += 1 |
|
715 client_nodeid += 1 |
|
716 ###################################### |
|
717 if child.PlugType == "ModbusRTUclient": |
|
718 rtuclient_reqs_count += len(child.IECSortedChildren()) |
|
719 new_node = GetRTUClientNodePrinted(self, child) |
|
720 if new_node is None: |
|
721 return [],"",False |
|
722 client_node_list.append(new_node) |
|
723 for subchild in child.IECSortedChildren(): |
|
724 new_req = GetClientRequestPrinted(self, subchild, client_nodeid) |
|
725 if new_req is None: |
|
726 return [],"",False |
|
727 client_request_list.append(new_req) |
|
728 for iecvar in subchild.GetLocations(): |
|
729 #absloute address - start address |
|
730 relative_addr = iecvar["LOC"][3] - int(subchild.GetParamsAttributes()[0]["children"][3]["value"]) |
|
731 #test if relative address in request specified range |
|
732 if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][2]["value"])): |
|
733 if str(iecvar["NAME"]) not in loc_vars_list: |
|
734 loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) |
|
735 loc_vars_list.append(str(iecvar["NAME"])) |
|
736 client_requestid += 1 |
|
737 rtuclient_node_count += 1 |
|
738 client_nodeid += 1 |
|
739 nodeid += 1 |
|
740 |
|
741 loc_dict["loc_vars"] = "\n".join(loc_vars) |
|
742 loc_dict["server_nodes_params"] = ",\n\n".join(server_node_list) |
|
743 loc_dict["client_nodes_params"] = ",\n\n".join(client_node_list) |
|
744 loc_dict["client_req_params"] = ",\n\n".join(client_request_list) |
|
745 loc_dict["tcpclient_reqs_count"] = str(tcpclient_reqs_count) |
|
746 loc_dict["tcpclient_node_count"] = str(tcpclient_node_count) |
|
747 loc_dict["tcpserver_node_count"] = str(tcpserver_node_count) |
|
748 loc_dict["rtuclient_reqs_count"] = str(rtuclient_reqs_count) |
|
749 loc_dict["rtuclient_node_count"] = str(rtuclient_node_count) |
|
750 loc_dict["rtuserver_node_count"] = str(rtuserver_node_count) |
|
751 loc_dict["ascclient_reqs_count"] = str(ascclient_reqs_count) |
|
752 loc_dict["ascclient_node_count"] = str(ascclient_node_count) |
|
753 loc_dict["ascserver_node_count"] = str(ascserver_node_count) |
|
754 loc_dict["total_tcpnode_count"] = str(total_node_count[0]) |
|
755 loc_dict["total_rtunode_count"] = str(total_node_count[1]) |
|
756 loc_dict["total_ascnode_count"] = str(total_node_count[2]) |
|
757 loc_dict["max_remote_tcpclient"] = int(self.GetParamsAttributes()[0]["children"][0]["value"]) |
|
758 |
|
759 #get template file content into a string, format it with dict |
|
760 #and write it to proper .h file |
|
761 mb_main = open(h_filename).read() % loc_dict |
|
762 f = open(Gen_MB_h_path,'w') |
|
763 f.write(mb_main) |
|
764 f.close() |
|
765 #same thing as above, but now to .c file |
|
766 mb_main = open(c_filename).read() % loc_dict |
|
767 f = open(Gen_MB_c_path,'w') |
|
768 f.write(mb_main) |
|
769 f.close() |
|
770 |
|
771 LDFLAGS = [] |
|
772 LDFLAGS.append(" \"-L" + ModbusPath + "\"") |
|
773 LDFLAGS.append(" -lmb ") |
|
774 LDFLAGS.append(" \"-Wl,-rpath," + ModbusPath + "\"") |
|
775 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave_and_master.o") + "\"") |
|
776 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave.o") + "\"") |
|
777 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_master.o") + "\"") |
|
778 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_tcp.o") + "\"") |
|
779 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_rtu.o") + "\"") |
|
780 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_ascii.o") + "\"") |
|
781 #LDFLAGS.append("\"" + os.path.join(ModbusPath, "sin_util.o") + "\"") |
|
782 if os.name == 'nt': # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos' |
|
783 LDFLAGS.append(" -lws2_32 ") # on windows we need to load winsock library! |
|
784 |
|
785 return [(Gen_MB_c_path, ' -I"'+ModbusPath+'"')], LDFLAGS, True |