|
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. |
|
6 # This files implements the bacnet plugin for Beremiz, adding BACnet server support. |
|
7 # |
|
8 # Copyright (C) 2017: Mario de Sousa (msousa@fe.up.pt) |
|
9 # |
|
10 # See COPYING file for copyrights details. |
|
11 # |
|
12 # This program is free software; you can redistribute it and/or |
|
13 # modify it under the terms of the GNU General Public License |
|
14 # as published by the Free Software Foundation; either version 2 |
|
15 # of the License, or (at your option) any later version. |
|
16 # |
|
17 # This program is distributed in the hope that it will be useful, |
|
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 # GNU General Public License for more details. |
|
21 # |
|
22 # You should have received a copy of the GNU General Public License |
|
23 # along with this program; if not, write to the Free Software |
|
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
25 |
|
26 import wx |
|
27 from collections import Counter |
|
28 from pickle import dump |
|
29 from util.BitmapLibrary import GetBitmap |
|
30 |
|
31 |
|
32 |
|
33 # Import some libraries on Beremiz code... |
|
34 from controls.CustomGrid import CustomGrid |
|
35 from controls.CustomTable import CustomTable |
|
36 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor |
|
37 from graphics.GraphicCommons import ERROR_HIGHLIGHT |
|
38 |
|
39 |
|
40 |
|
41 # BACnet Engineering units taken from: ASHRAE 135-2016, clause/chapter 21 |
|
42 BACnetEngineeringUnits = [ |
|
43 ('(Acceleration) meters-per-second-per-second (166)', 166 ), |
|
44 ('(Area) square-meters (0)', 0 ), |
|
45 ('(Area) square-centimeters (116)', 116 ), |
|
46 ('(Area) square-feet (1)', 1 ), |
|
47 ('(Area) square-inches (115)', 115 ), |
|
48 ('(Currency) currency1 (105)', 105 ), |
|
49 ('(Currency) currency2 (106)', 106 ), |
|
50 ('(Currency) currency3 (107)', 107 ), |
|
51 ('(Currency) currency4 (108)', 108 ), |
|
52 ('(Currency) currency5 (109)', 109 ), |
|
53 ('(Currency) currency6 (110)', 110 ), |
|
54 ('(Currency) currency7 (111)', 111 ), |
|
55 ('(Currency) currency8 (112)', 112 ), |
|
56 ('(Currency) currency9 (113)', 113 ), |
|
57 ('(Currency) currency10 (114)', 114 ), |
|
58 ('(Electrical) milliamperes (2)', 2 ), |
|
59 ('(Electrical) amperes (3)', 3 ), |
|
60 ('(Electrical) amperes-per-meter (167)', 167 ), |
|
61 ('(Electrical) amperes-per-square-meter (168)', 168 ), |
|
62 ('(Electrical) ampere-square-meters (169)', 169 ), |
|
63 ('(Electrical) decibels (199)', 199 ), |
|
64 ('(Electrical) decibels-millivolt (200)', 200 ), |
|
65 ('(Electrical) decibels-volt (201)', 201 ), |
|
66 ('(Electrical) farads (170)', 170 ), |
|
67 ('(Electrical) henrys (171)', 171 ), |
|
68 ('(Electrical) ohms (4)', 4 ), |
|
69 ('(Electrical) ohm-meter-squared-per-meter (237)', 237 ), |
|
70 ('(Electrical) ohm-meters (172)', 172 ), |
|
71 ('(Electrical) milliohms (145)', 145 ), |
|
72 ('(Electrical) kilohms (122)', 122 ), |
|
73 ('(Electrical) megohms (123)', 123 ), |
|
74 ('(Electrical) microsiemens (190)', 190 ), |
|
75 ('(Electrical) millisiemens (202)', 202 ), |
|
76 ('(Electrical) siemens (173)', 173 ), |
|
77 ('(Electrical) siemens-per-meter (174)', 174 ), |
|
78 ('(Electrical) teslas (175)', 175 ), |
|
79 ('(Electrical) volts (5)', 5 ), |
|
80 ('(Electrical) millivolts (124)', 124 ), |
|
81 ('(Electrical) kilovolts (6)', 6 ), |
|
82 ('(Electrical) megavolts (7)', 7 ), |
|
83 ('(Electrical) volt-amperes (8)', 8 ), |
|
84 ('(Electrical) kilovolt-amperes (9)', 9 ), |
|
85 ('(Electrical) megavolt-amperes (10)', 10 ), |
|
86 ('(Electrical) volt-amperes-reactive (11)', 11 ), |
|
87 ('(Electrical) kilovolt-amperes-reactive (12)', 12 ), |
|
88 ('(Electrical) megavolt-amperes-reactive (13)', 13 ), |
|
89 ('(Electrical) volts-per-degree-kelvin (176)', 176 ), |
|
90 ('(Electrical) volts-per-meter (177)', 177 ), |
|
91 ('(Electrical) degrees-phase (14)', 14 ), |
|
92 ('(Electrical) power-factor (15)', 15 ), |
|
93 ('(Electrical) webers (178)', 178 ), |
|
94 ('(Energy) ampere-seconds (238)', 238 ), |
|
95 ('(Energy) volt-ampere-hours (239)', 239 ), |
|
96 ('(Energy) kilovolt-ampere-hours (240)', 240 ), |
|
97 ('(Energy) megavolt-ampere-hours (241)', 241 ), |
|
98 ('(Energy) volt-ampere-hours-reactive (242)', 242 ), |
|
99 ('(Energy) kilovolt-ampere-hours-reactive (243)', 243 ), |
|
100 ('(Energy) megavolt-ampere-hours-reactive (244)', 244 ), |
|
101 ('(Energy) volt-square-hours (245)', 245 ), |
|
102 ('(Energy) ampere-square-hours (246)', 246 ), |
|
103 ('(Energy) joules (16)', 16 ), |
|
104 ('(Energy) kilojoules (17)', 17 ), |
|
105 ('(Energy) kilojoules-per-kilogram (125)', 125 ), |
|
106 ('(Energy) megajoules (126)', 126 ), |
|
107 ('(Energy) watt-hours (18)', 18 ), |
|
108 ('(Energy) kilowatt-hours (19)', 19 ), |
|
109 ('(Energy) megawatt-hours (146)', 146 ), |
|
110 ('(Energy) watt-hours-reactive (203)', 203 ), |
|
111 ('(Energy) kilowatt-hours-reactive (204)', 204 ), |
|
112 ('(Energy) megawatt-hours-reactive (205)', 205 ), |
|
113 ('(Energy) btus (20)', 20 ), |
|
114 ('(Energy) kilo-btus (147)', 147 ), |
|
115 ('(Energy) mega-btus (148)', 148 ), |
|
116 ('(Energy) therms (21)', 21 ), |
|
117 ('(Energy) ton-hours (22)', 22 ), |
|
118 ('(Enthalpy) joules-per-kilogram-dry-air (23)', 23 ), |
|
119 ('(Enthalpy) kilojoules-per-kilogram-dry-air (149)', 149 ), |
|
120 ('(Enthalpy) megajoules-per-kilogram-dry-air (150)', 150 ), |
|
121 ('(Enthalpy) btus-per-pound-dry-air (24)', 24 ), |
|
122 ('(Enthalpy) btus-per-pound (117)', 117 ), |
|
123 ('(Entropy) joules-per-degree-kelvin (127)', 127 ), |
|
124 ('(Entropy) kilojoules-per-degree-kelvin (151)', 151 ), |
|
125 ('(Entropy) megajoules-per-degree-kelvin (152)', 152 ), |
|
126 ('(Entropy) joules-per-kilogram-degree-kelvin (128)', 128 ), |
|
127 ('(Force) newton (153)', 153 ), |
|
128 ('(Frequency) cycles-per-hour (25)', 25 ), |
|
129 ('(Frequency) cycles-per-minute (26)', 26 ), |
|
130 ('(Frequency) hertz (27)', 27 ), |
|
131 ('(Frequency) kilohertz (129)', 129 ), |
|
132 ('(Frequency) megahertz (130)', 130 ), |
|
133 ('(Frequency) per-hour (131)', 131 ), |
|
134 ('(Humidity) grams-of-water-per-kilogram-dry-air (28)', 28 ), |
|
135 ('(Humidity) percent-relative-humidity (29)', 29 ), |
|
136 ('(Length) micrometers (194)', 194 ), |
|
137 ('(Length) millimeters (30)', 30 ), |
|
138 ('(Length) centimeters (118)', 118 ), |
|
139 ('(Length) kilometers (193)', 193 ), |
|
140 ('(Length) meters (31)', 31 ), |
|
141 ('(Length) inches (32)', 32 ), |
|
142 ('(Length) feet (33)', 33 ), |
|
143 ('(Light) candelas (179)', 179 ), |
|
144 ('(Light) candelas-per-square-meter (180)', 180 ), |
|
145 ('(Light) watts-per-square-foot (34)', 34 ), |
|
146 ('(Light) watts-per-square-meter (35)', 35 ), |
|
147 ('(Light) lumens (36)', 36 ), |
|
148 ('(Light) luxes (37)', 37 ), |
|
149 ('(Light) foot-candles (38)', 38 ), |
|
150 ('(Mass) milligrams (196)', 196 ), |
|
151 ('(Mass) grams (195)', 195 ), |
|
152 ('(Mass) kilograms (39)', 39 ), |
|
153 ('(Mass) pounds-mass (40)', 40 ), |
|
154 ('(Mass) tons (41)', 41 ), |
|
155 ('(Mass Flow) grams-per-second (154)', 154 ), |
|
156 ('(Mass Flow) grams-per-minute (155)', 155 ), |
|
157 ('(Mass Flow) kilograms-per-second (42)', 42 ), |
|
158 ('(Mass Flow) kilograms-per-minute (43)', 43 ), |
|
159 ('(Mass Flow) kilograms-per-hour (44)', 44 ), |
|
160 ('(Mass Flow) pounds-mass-per-second (119)', 119 ), |
|
161 ('(Mass Flow) pounds-mass-per-minute (45)', 45 ), |
|
162 ('(Mass Flow) pounds-mass-per-hour (46)', 46 ), |
|
163 ('(Mass Flow) tons-per-hour (156)', 156 ), |
|
164 ('(Power) milliwatts (132)', 132 ), |
|
165 ('(Power) watts (47)', 47 ), |
|
166 ('(Power) kilowatts (48)', 48 ), |
|
167 ('(Power) megawatts (49)', 49 ), |
|
168 ('(Power) btus-per-hour (50)', 50 ), |
|
169 ('(Power) kilo-btus-per-hour (157)', 157 ), |
|
170 ('(Power) joule-per-hours (247)', 247 ), |
|
171 ('(Power) horsepower (51)', 51 ), |
|
172 ('(Power) tons-refrigeration (52)', 52 ), |
|
173 ('(Pressure) pascals (53)', 53 ), |
|
174 ('(Pressure) hectopascals (133)', 133 ), |
|
175 ('(Pressure) kilopascals (54)', 54 ), |
|
176 ('(Pressure) millibars (134)', 134 ), |
|
177 ('(Pressure) bars (55)', 55 ), |
|
178 ('(Pressure) pounds-force-per-square-inch (56)', 56 ), |
|
179 ('(Pressure) millimeters-of-water (206)', 206 ), |
|
180 ('(Pressure) centimeters-of-water (57)', 57 ), |
|
181 ('(Pressure) inches-of-water (58)', 58 ), |
|
182 ('(Pressure) millimeters-of-mercury (59)', 59 ), |
|
183 ('(Pressure) centimeters-of-mercury (60)', 60 ), |
|
184 ('(Pressure) inches-of-mercury (61)', 61 ), |
|
185 ('(Temperature) degrees-celsius (62)', 62 ), |
|
186 ('(Temperature) degrees-kelvin (63)', 63 ), |
|
187 ('(Temperature) degrees-kelvin-per-hour (181)', 181 ), |
|
188 ('(Temperature) degrees-kelvin-per-minute (182)', 182 ), |
|
189 ('(Temperature) degrees-fahrenheit (64)', 64 ), |
|
190 ('(Temperature) degree-days-celsius (65)', 65 ), |
|
191 ('(Temperature) degree-days-fahrenheit (66)', 66 ), |
|
192 ('(Temperature) delta-degrees-fahrenheit (120)', 120 ), |
|
193 ('(Temperature) delta-degrees-kelvin (121)', 121 ), |
|
194 ('(Time) years (67)', 67 ), |
|
195 ('(Time) months (68)', 68 ), |
|
196 ('(Time) weeks (69)', 69 ), |
|
197 ('(Time) days (70)', 70 ), |
|
198 ('(Time) hours (71)', 71 ), |
|
199 ('(Time) minutes (72)', 72 ), |
|
200 ('(Time) seconds (73)', 73 ), |
|
201 ('(Time) hundredths-seconds (158)', 158 ), |
|
202 ('(Time) milliseconds (159)', 159 ), |
|
203 ('(Torque) newton-meters (160)', 160 ), |
|
204 ('(Velocity) millimeters-per-second (161)', 161 ), |
|
205 ('(Velocity) millimeters-per-minute (162)', 162 ), |
|
206 ('(Velocity) meters-per-second (74)', 74 ), |
|
207 ('(Velocity) meters-per-minute (163)', 163 ), |
|
208 ('(Velocity) meters-per-hour (164)', 164 ), |
|
209 ('(Velocity) kilometers-per-hour (75)', 75 ), |
|
210 ('(Velocity) feet-per-second (76)', 76 ), |
|
211 ('(Velocity) feet-per-minute (77)', 77 ), |
|
212 ('(Velocity) miles-per-hour (78)', 78 ), |
|
213 ('(Volume) cubic-feet (79)', 79 ), |
|
214 ('(Volume) cubic-meters (80)', 80 ), |
|
215 ('(Volume) imperial-gallons (81)', 81 ), |
|
216 ('(Volume) milliliters (197)', 197 ), |
|
217 ('(Volume) liters (82)', 82 ), |
|
218 ('(Volume) us-gallons (83)', 83 ), |
|
219 ('(Volumetric Flow) cubic-feet-per-second (142)', 142 ), |
|
220 ('(Volumetric Flow) cubic-feet-per-minute (84)', 84 ), |
|
221 ('(Volumetric Flow) million-standard-cubic-feet-per-minute (254)', 254 ), |
|
222 ('(Volumetric Flow) cubic-feet-per-hour (191)', 191 ), |
|
223 ('(Volumetric Flow) cubic-feet-per-day (248)', 248 ), |
|
224 ('(Volumetric Flow) standard-cubic-feet-per-day (47808)', 47808 ), |
|
225 ('(Volumetric Flow) million-standard-cubic-feet-per-day (47809)', 47809 ), |
|
226 ('(Volumetric Flow) thousand-cubic-feet-per-day (47810)', 47810 ), |
|
227 ('(Volumetric Flow) thousand-standard-cubic-feet-per-day (47811)', 47811 ), |
|
228 ('(Volumetric Flow) pounds-mass-per-day (47812)', 47812 ), |
|
229 ('(Volumetric Flow) cubic-meters-per-second (85)', 85 ), |
|
230 ('(Volumetric Flow) cubic-meters-per-minute (165)', 165 ), |
|
231 ('(Volumetric Flow) cubic-meters-per-hour (135)', 135 ), |
|
232 ('(Volumetric Flow) cubic-meters-per-day (249)', 249 ), |
|
233 ('(Volumetric Flow) imperial-gallons-per-minute (86)', 86 ), |
|
234 ('(Volumetric Flow) milliliters-per-second (198)', 198 ), |
|
235 ('(Volumetric Flow) liters-per-second (87)', 87 ), |
|
236 ('(Volumetric Flow) liters-per-minute (88)', 88 ), |
|
237 ('(Volumetric Flow) liters-per-hour (136)', 136 ), |
|
238 ('(Volumetric Flow) us-gallons-per-minute (89)', 89 ), |
|
239 ('(Volumetric Flow) us-gallons-per-hour (192)', 192 ), |
|
240 ('(Other) degrees-angular (90)', 90 ), |
|
241 ('(Other) degrees-celsius-per-hour (91)', 91 ), |
|
242 ('(Other) degrees-celsius-per-minute (92)', 92 ), |
|
243 ('(Other) degrees-fahrenheit-per-hour (93)', 93 ), |
|
244 ('(Other) degrees-fahrenheit-per-minute (94)', 94 ), |
|
245 ('(Other) joule-seconds (183)', 183 ), |
|
246 ('(Other) kilograms-per-cubic-meter (186)', 186 ), |
|
247 ('(Other) kilowatt-hours-per-square-meter (137)', 137 ), |
|
248 ('(Other) kilowatt-hours-per-square-foot (138)', 138 ), |
|
249 ('(Other) watt-hours-per-cubic-meter (250)', 250 ), |
|
250 ('(Other) joules-per-cubic-meter (251)', 251 ), |
|
251 ('(Other) megajoules-per-square-meter (139)', 139 ), |
|
252 ('(Other) megajoules-per-square-foot (140)', 140 ), |
|
253 ('(Other) mole-percent (252)', 252 ), |
|
254 ('(Other) no-units (95)', 95 ), |
|
255 ('(Other) newton-seconds (187)', 187 ), |
|
256 ('(Other) newtons-per-meter (188)', 188 ), |
|
257 ('(Other) parts-per-million (96)', 96 ), |
|
258 ('(Other) parts-per-billion (97)', 97 ), |
|
259 ('(Other) pascal-seconds (253)', 253 ), |
|
260 ('(Other) percent (98)', 98 ), |
|
261 ('(Other) percent-obscuration-per-foot (143)', 143 ), |
|
262 ('(Other) percent-obscuration-per-meter (144)', 144 ), |
|
263 ('(Other) percent-per-second (99)', 99 ), |
|
264 ('(Other) per-minute (100)', 100 ), |
|
265 ('(Other) per-second (101)', 101 ), |
|
266 ('(Other) psi-per-degree-fahrenheit (102)', 102 ), |
|
267 ('(Other) radians (103)', 103 ), |
|
268 ('(Other) radians-per-second (184)', 184 ), |
|
269 ('(Other) revolutions-per-minute (104)', 104 ), |
|
270 ('(Other) square-meters-per-newton (185)', 185 ), |
|
271 ('(Other) watts-per-meter-per-degree-kelvin (189)', 189 ), |
|
272 ('(Other) watts-per-square-meter-degree-kelvin (141)', 141 ), |
|
273 ('(Other) per-mille (207)', 207 ), |
|
274 ('(Other) grams-per-gram (208)', 208 ), |
|
275 ('(Other) kilograms-per-kilogram (209)', 209 ), |
|
276 ('(Other) grams-per-kilogram (210)', 210 ), |
|
277 ('(Other) milligrams-per-gram (211)', 211 ), |
|
278 ('(Other) milligrams-per-kilogram (212)', 212 ), |
|
279 ('(Other) grams-per-milliliter (213)', 213 ), |
|
280 ('(Other) grams-per-liter (214)', 214 ), |
|
281 ('(Other) milligrams-per-liter (215)', 215 ), |
|
282 ('(Other) micrograms-per-liter (216)', 216 ), |
|
283 ('(Other) grams-per-cubic-meter (217)', 217 ), |
|
284 ('(Other) milligrams-per-cubic-meter (218)', 218 ), |
|
285 ('(Other) micrograms-per-cubic-meter (219)', 219 ), |
|
286 ('(Other) nanograms-per-cubic-meter (220)', 220 ), |
|
287 ('(Other) grams-per-cubic-centimeter (221)', 221 ), |
|
288 ('(Other) becquerels (222)', 222 ), |
|
289 ('(Other) kilobecquerels (223)', 223 ), |
|
290 ('(Other) megabecquerels (224)', 224 ), |
|
291 ('(Other) gray (225)', 225 ), |
|
292 ('(Other) milligray (226)', 226 ), |
|
293 ('(Other) microgray (227)', 227 ), |
|
294 ('(Other) sieverts (228)', 228 ), |
|
295 ('(Other) millisieverts (229)', 229 ), |
|
296 ('(Other) microsieverts (230)', 230 ), |
|
297 ('(Other) microsieverts-per-hour (231)', 231 ), |
|
298 ('(Other) millirems (47814)', 47814 ), |
|
299 ('(Other) millirems-per-hour (47815)', 47815 ), |
|
300 ('(Other) decibels-a (232)', 232 ), |
|
301 ('(Other) nephelometric-turbidity-unit (233)', 233 ), |
|
302 ('(Other) pH (234)', 234 ), |
|
303 ('(Other) grams-per-square-meter (235)', 235 ), |
|
304 ('(Other) minutes-per-degree-kelvin (236)', 236 ) |
|
305 ] # BACnetEngineeringUnits |
|
306 |
|
307 |
|
308 |
|
309 # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 = 4194303 |
|
310 # However, ObjectID 4194303 is not allowed! |
|
311 # 4194303 is used as a special value when object Id reference is referencing an undefined object |
|
312 # (similar to NULL in C) |
|
313 BACnetObjectID_MAX = 4194302 |
|
314 BACnetObjectID_NUL = 4194303 |
|
315 |
|
316 |
|
317 |
|
318 # A base class |
|
319 # what would be a purely virtual class in C++ |
|
320 class ObjectProperties: |
|
321 # this __init_() function is currently not beeing used! |
|
322 def __init__(self): |
|
323 #nothing to do |
|
324 return |
|
325 |
|
326 |
|
327 class BinaryObject(ObjectProperties): |
|
328 # 'PropertyNames' will be used as the header for each column of the Object Properties grid! |
|
329 # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" |
|
330 # Be sure to use these exact names for these BACnet object properties! |
|
331 PropertyNames = ["Object Identifier", "Object Name", "Description"] |
|
332 ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT] |
|
333 ColumnSizes = [ 40 , 80 , 80 ] |
|
334 PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor, |
|
335 "GridCellRenderer" : wx.grid.GridCellNumberRenderer, |
|
336 "GridCellEditorParam": "0,4194302" |
|
337 # syntax for GridCellNumberEditor -> "min,max" |
|
338 # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 |
|
339 }, |
|
340 "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
341 "GridCellRenderer": wx.grid.GridCellStringRenderer}, |
|
342 "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
343 "GridCellRenderer": wx.grid.GridCellStringRenderer} |
|
344 } |
|
345 |
|
346 class AnalogObject(ObjectProperties): |
|
347 # 'PropertyNames' will be used as the header for each column of the Object Properties grid! |
|
348 # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" |
|
349 # Be sure to use these exact names for these BACnet object properties! |
|
350 # |
|
351 # NOTE: Although it is not listed here (so it does not show up in the GUI, this object will also |
|
352 # keep another entry for a virtual property named "Unit ID". This virtual property |
|
353 # will store the ID corresponding to the "Engineering Units" currently chosen. |
|
354 # This virtual property is kept synchronised to the "Engineering Units" property |
|
355 # by the function PropertyChanged() which should be called by the OnCellChange event handler. |
|
356 PropertyNames = ["Object Identifier", "Object Name", "Description", "Engineering Units"] #'Unit ID' |
|
357 ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT ] |
|
358 ColumnSizes = [ 40 , 80 , 80 , 200 ] |
|
359 PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor, |
|
360 "GridCellRenderer" : wx.grid.GridCellNumberRenderer, |
|
361 "GridCellEditorParam": "0,4194302" |
|
362 # syntax for GridCellNumberEditor -> "min,max" |
|
363 # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 |
|
364 }, |
|
365 "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
366 "GridCellRenderer" : wx.grid.GridCellStringRenderer}, |
|
367 "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
368 "GridCellRenderer" : wx.grid.GridCellStringRenderer}, |
|
369 "Engineering Units": {"GridCellEditor" : wx.grid.GridCellChoiceEditor, |
|
370 "GridCellRenderer" : wx.grid.GridCellStringRenderer, |
|
371 # use string renderer with choice editor! |
|
372 "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits]) |
|
373 # syntax for GridCellChoiceEditor -> comma separated values |
|
374 } |
|
375 } |
|
376 |
|
377 # obj_properties should be a dictionary, with keys "Object Identifier", "Object Name", "Description", ... |
|
378 def UpdateVirtualProperties(self, obj_properties): |
|
379 obj_properties["Unit ID"] = [x[1] for x in BACnetEngineeringUnits if x[0] == obj_properties["Engineering Units"]][0] |
|
380 |
|
381 |
|
382 |
|
383 class MultiSObject(ObjectProperties): |
|
384 # 'PropertyNames' will be used as the header for each column of the Object Properties grid! |
|
385 # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" |
|
386 # Be sure to use these exact names for these BACnet object properties! |
|
387 PropertyNames = ["Object Identifier", "Object Name", "Description", "Number of States"] |
|
388 ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_CENTER ] |
|
389 ColumnSizes = [ 40 , 80 , 80 , 120 ] |
|
390 PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor, |
|
391 "GridCellRenderer" : wx.grid.GridCellNumberRenderer, |
|
392 "GridCellEditorParam": "0,4194302" |
|
393 # syntax for GridCellNumberEditor -> "min,max" |
|
394 # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 |
|
395 }, |
|
396 "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
397 "GridCellRenderer" : wx.grid.GridCellStringRenderer}, |
|
398 "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor, |
|
399 "GridCellRenderer" : wx.grid.GridCellStringRenderer}, |
|
400 "Number of States" : {"GridCellEditor" : wx.grid.GridCellNumberEditor, |
|
401 "GridCellRenderer" : wx.grid.GridCellNumberRenderer, |
|
402 "GridCellEditorParam": "1,255" # syntax for GridCellNumberEditor -> "min,max" |
|
403 # MultiState Values are encoded in unsigned integer |
|
404 # (in BACnet => uint8_t), and can not be 0. |
|
405 # See ASHRAE 135-2016, section 12.20.4 |
|
406 } |
|
407 } |
|
408 |
|
409 |
|
410 |
|
411 # The default values to use for each BACnet object type |
|
412 # |
|
413 # Note that the 'internal plugin parameters' get stored in the data table, but |
|
414 # are not visible in the GUI. They are used to generate the |
|
415 # EDE files as well as the C code |
|
416 class BVObject(BinaryObject): |
|
417 DefaultValues = {"Object Identifier":"", |
|
418 "Object Name" :"Binary Value", |
|
419 "Description" :"", |
|
420 # internal plugin parameters... |
|
421 "BACnetObjTypeID" :5, |
|
422 "Ctype" :"uint8_t", |
|
423 "Settable" :"Y" |
|
424 } |
|
425 |
|
426 class BOObject(BinaryObject): |
|
427 DefaultValues = {"Object Identifier":"", |
|
428 "Object Name" :"Binary Output", |
|
429 "Description" :"", |
|
430 # internal plugin parameters... |
|
431 "BACnetObjTypeID" :4, |
|
432 "Ctype" :"uint8_t", |
|
433 "Settable" :"Y" |
|
434 } |
|
435 |
|
436 class BIObject(BinaryObject): |
|
437 DefaultValues = {"Object Identifier":"", |
|
438 "Object Name" :"Binary Input", |
|
439 "Description" :"", |
|
440 # internal plugin parameters... |
|
441 "BACnetObjTypeID" :3, |
|
442 "Ctype" :"uint8_t", |
|
443 "Settable" :"N" |
|
444 } |
|
445 |
|
446 class AVObject(AnalogObject): |
|
447 DefaultValues = {"Object Identifier":"", |
|
448 "Object Name" :"Analog Value", |
|
449 "Description" :"", |
|
450 "Engineering Units":'(Other) no-units (95)', |
|
451 # internal plugin parameters... |
|
452 "Unit ID" :95, # the ID of the engineering unit |
|
453 # will get updated by UpdateVirtualProperties() |
|
454 "BACnetObjTypeID" :2, |
|
455 "Ctype" :"float", |
|
456 "Settable" :"Y" |
|
457 } |
|
458 |
|
459 class AOObject(AnalogObject): |
|
460 DefaultValues = {"Object Identifier":"", |
|
461 "Object Name" :"Analog Output", |
|
462 "Description" :"", |
|
463 "Engineering Units":'(Other) no-units (95)', |
|
464 # internal plugin parameters... |
|
465 "Unit ID" :95, # the ID of the engineering unit |
|
466 # will get updated by UpdateVirtualProperties() |
|
467 "BACnetObjTypeID" :1, |
|
468 "Ctype" :"float", |
|
469 "Settable" :"Y" |
|
470 } |
|
471 |
|
472 class AIObject(AnalogObject): |
|
473 DefaultValues = {"Object Identifier":"", |
|
474 "Object Name" :"Analog Input", |
|
475 "Description" :"", |
|
476 "Engineering Units":'(Other) no-units (95)', |
|
477 # internal plugin parameters... |
|
478 "Unit ID" :95, # the ID of the engineering unit |
|
479 # will get updated by UpdateVirtualProperties() |
|
480 "BACnetObjTypeID" :0, |
|
481 "Ctype" :"float", |
|
482 "Settable" :"N" |
|
483 } |
|
484 |
|
485 class MSVObject(MultiSObject): |
|
486 DefaultValues = {"Object Identifier":"", |
|
487 "Object Name" :"Multi-state Value", |
|
488 "Description" :"", |
|
489 "Number of States" :"255", |
|
490 # internal plugin parameters... |
|
491 "BACnetObjTypeID" :19, |
|
492 "Ctype" :"uint8_t", |
|
493 "Settable" :"Y" |
|
494 } |
|
495 |
|
496 class MSOObject(MultiSObject): |
|
497 DefaultValues = {"Object Identifier":"", |
|
498 "Object Name" :"Multi-state Output", |
|
499 "Description" :"", |
|
500 "Number of States" :"255", |
|
501 # internal plugin parameters... |
|
502 "BACnetObjTypeID" :14, |
|
503 "Ctype" :"uint8_t", |
|
504 "Settable" :"Y" |
|
505 } |
|
506 |
|
507 class MSIObject(MultiSObject): |
|
508 DefaultValues = {"Object Identifier":"", |
|
509 "Object Name" :"Multi-state Input", |
|
510 "Description" :"", |
|
511 "Number of States" :"255", |
|
512 # internal plugin parameters... |
|
513 "BACnetObjTypeID" :13, |
|
514 "Ctype" :"uint8_t", |
|
515 "Settable" :"N" |
|
516 } |
|
517 |
|
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 class ObjectTable(CustomTable): |
|
528 # A custom wx.grid.PyGridTableBase using user supplied data |
|
529 # |
|
530 # This will basically store a list of BACnet objects that the slave will support/implement. |
|
531 # There will be one instance of this ObjectTable class for each BACnet object type |
|
532 # (e.g. Binary Value, Analog Input, Multi State Output, ...) |
|
533 # |
|
534 # The list of BACnet objects will actually be stored within the self.data variable |
|
535 # (declared in CustomTable). Self.data will be a list of dictionaries (one entry per BACnet |
|
536 # object). All of these dictionaries in the self.data list will have entries whose keys actually |
|
537 # depend on the BACnet type object being handled. The keys in the dictionary will be |
|
538 # the entries in the PropertyNames list of one of the following classes: |
|
539 # (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject). |
|
540 # |
|
541 # For example, when handling Binary Value BACnet objects, |
|
542 # self.data will be a list of dictionaries (one entry per row) |
|
543 # self.data[n] will be a dictionary, with keys "Object Identifier", "Object Name", "Description" |
|
544 # for example: self.data[n] = {"Object Identifier":33, "Object Name":"room1", "Description":"xx"} |
|
545 # |
|
546 # Note that this ObjectTable class merely stores the configuration data. |
|
547 # It does not control the display nor the editing of this data. |
|
548 # This data is typically displayed within a grid, that is controlled by the ObjectGrid class. |
|
549 # |
|
550 def __init__(self, parent, data, BACnetObjectType): |
|
551 # parent : the _BacnetSlavePlug object that is instantiating this ObjectTable |
|
552 # data : a list with the data to be shown on the grid |
|
553 # (i.e., a list containing the BACnet object properties) |
|
554 # Instantiated in _BacnetSlavePlug |
|
555 # BACnetObjectType: one of BinaryObject, AnalogObject, MultiSObject |
|
556 # (or a class that derives from them). |
|
557 # This is actually the class itself, and not a variable!!! |
|
558 # However, self.BACnetObjectType will be an instance |
|
559 # of said class as we later need to call methods from this class. |
|
560 # (in particular, the UpdateVirtualProperties() method) |
|
561 # |
|
562 # The base class must be initialized *first* |
|
563 CustomTable.__init__(self, parent, data, BACnetObjectType.PropertyNames) |
|
564 self.BACnetObjectType = BACnetObjectType() |
|
565 self.ChangesToSave = False |
|
566 |
|
567 #def _GetRowEdit(self, row): |
|
568 #row_edit = self.GetValueByName(row, "Edit") |
|
569 #var_type = self.Parent.GetTagName() |
|
570 #bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type) |
|
571 #if bodytype in ["ST", "IL"]: |
|
572 #row_edit = True; |
|
573 #return row_edit |
|
574 |
|
575 def _updateColAttrs(self, grid): |
|
576 # wx.grid.Grid -> update the column attributes to add the |
|
577 # appropriate renderer given the column name. |
|
578 # |
|
579 # Otherwise default to the default renderer. |
|
580 #print "ObjectTable._updateColAttrs() called!!!" |
|
581 for row in range(self.GetNumberRows()): |
|
582 for col in range(self.GetNumberCols()): |
|
583 PropertyName = self.BACnetObjectType.PropertyNames[col] |
|
584 PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName] |
|
585 grid.SetReadOnly (row, col, False) |
|
586 grid.SetCellEditor (row, col, PropertyConfig["GridCellEditor"] ()) |
|
587 grid.SetCellRenderer (row, col, PropertyConfig["GridCellRenderer"]()) |
|
588 grid.SetCellBackgroundColour(row, col, wx.WHITE) |
|
589 grid.SetCellTextColour (row, col, wx.BLACK) |
|
590 if "GridCellEditorParam" in PropertyConfig: |
|
591 grid.GetCellEditor(row, col).SetParameters(PropertyConfig["GridCellEditorParam"]) |
|
592 self.ResizeRow(grid, row) |
|
593 |
|
594 def FindValueByName(self, PropertyName, PropertyValue): |
|
595 # find the row whose property named PropertyName has the value PropertyValue |
|
596 # Returns: row number |
|
597 # for example, find the row where PropertyName "Object Identifier" has value 1002 |
|
598 # FindValueByName("Object Identifier", 1002). |
|
599 for row in range(self.GetNumberRows()): |
|
600 if int(self.GetValueByName(row, PropertyName)) == PropertyValue: |
|
601 return row |
|
602 return None |
|
603 |
|
604 # Return a list containing all the values under the column named 'colname' |
|
605 def GetAllValuesByName(self, colname): |
|
606 values = [] |
|
607 for row in range(self.GetNumberRows()): |
|
608 values.append(self.data[row].get(colname)) |
|
609 return values |
|
610 |
|
611 # Returns a dictionary with: |
|
612 # keys: IDs of BACnet objects |
|
613 # value: number of BACnet objects using this same Id |
|
614 # (values larger than 1 indicates an error as BACnet requires unique |
|
615 # object IDs for objects of the same type) |
|
616 def GetObjectIDCount(self): |
|
617 # The dictionary is built by first creating a list containing the IDs of all BACnet objects |
|
618 ObjectIDs = self.GetAllValuesByName("Object Identifier") |
|
619 ObjectIDs_as_int = [int(x) for x in ObjectIDs] # list of integers instead of strings... |
|
620 # This list is then transformed into a collections.Counter class |
|
621 # Which is then transformed into a dictionary using dict() |
|
622 return dict(Counter(ObjectIDs_as_int)) |
|
623 |
|
624 # Check whether any object ID is used more than once (not valid in BACnet) |
|
625 # (returns True or False) |
|
626 def HasDuplicateObjectIDs(self): |
|
627 ObjectIDsCount = self.GetObjectIDCount() |
|
628 for ObjName in ObjectIDsCount: |
|
629 if ObjectIDsCount[ObjName] > 1: |
|
630 return True |
|
631 return False |
|
632 |
|
633 # Update the virtual properties of the objects of the classes derived from ObjectProperties |
|
634 # (currently only the AnalogObject class had virtua properties, i.e. a property |
|
635 # that is determined/calculated based on the other properties) |
|
636 def UpdateAllVirtualProperties(self): |
|
637 if hasattr(self.BACnetObjectType, 'UpdateVirtualProperties'): |
|
638 for ObjProp in self.data: |
|
639 self.BACnetObjectType.UpdateVirtualProperties(ObjProp) |
|
640 |
|
641 |
|
642 |
|
643 class ObjectGrid(CustomGrid): |
|
644 # A custom wx.grid.Grid (CustomGrid derives from wx.grid.Grid) |
|
645 # |
|
646 # Needed mostly to customise the initial values of newly added rows, and to |
|
647 # validate if the inserted data follows BACnet rules. |
|
648 # |
|
649 # |
|
650 # This ObjectGrid class: |
|
651 # Creates and controls the GUI __grid__ for configuring all the BACnet objects of one |
|
652 # (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...) |
|
653 # This grid is currently displayed within one 'window' controlled by a ObjectEditor |
|
654 # object (this organization is not likely to change in the future). |
|
655 # |
|
656 # The grid uses one line/row per BACnet object, and one column for each property of the BACnet |
|
657 # object. The column titles change depending on the specific type of BACnet object being edited |
|
658 # (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject). |
|
659 # The editor to use for each column is also obtained from that class (e.g. TextEditor, |
|
660 # NumberEditor, ...) |
|
661 # |
|
662 # This class does NOT store the data in the grid. It merely controls its display and editing. |
|
663 # The data in the grid is stored within an object of class ObjectTable |
|
664 # |
|
665 def __init__(self, *args, **kwargs): |
|
666 CustomGrid.__init__(self, *args, **kwargs) |
|
667 |
|
668 # Called when a new row is added by clicking Add button |
|
669 # call graph: CustomGrid.OnAddButton() --> CustomGrid.AddRow() --> ObjectGrid._AddRow() |
|
670 def _AddRow(self, new_row): |
|
671 if new_row > 0: |
|
672 self.Table.InsertRow(new_row, self.Table.GetRow(new_row - 1).copy()) |
|
673 else: |
|
674 self.Table.InsertRow(new_row, self.DefaultValue.copy()) |
|
675 self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL) # start off with invalid object ID |
|
676 # Find an apropriate BACnet object ID for the new object. |
|
677 # We choose a first attempt (based on object ID of previous line + 1) |
|
678 new_object_id = 0 |
|
679 if new_row > 0: |
|
680 new_object_id = int(self.Table.GetValueByName(new_row - 1, "Object Identifier")) |
|
681 new_object_id += 1 |
|
682 # Check whether the chosen object ID is not already in use. |
|
683 # If in use, add 1 to the attempted object ID and recheck... |
|
684 while self.Table.FindValueByName("Object Identifier", new_object_id) is not None: |
|
685 new_object_id += 1 |
|
686 # if reached end of object IDs, cycle back to 0 |
|
687 # (remember, we may have started at any inital object ID > 0, so it makes sense to cyclce back to 0) |
|
688 # warning: We risk entering an inifinite loop if all object IDs are already used. |
|
689 # The likelyhood of this happening is extremely low, (we would need 2^22 elements in the table!) |
|
690 # so don't worry about it for now. |
|
691 if new_object_id > BACnetObjectID_MAX: |
|
692 new_object_id = 0 |
|
693 # Set the object ID of the new object to the apropriate value |
|
694 # ... and append the ID to the default object name (so the name becomes unique) |
|
695 new_object_name = self.DefaultValue.get("Object Name") + " " + str(new_object_id) |
|
696 self.Table.SetValueByName(new_row, "Object Name" , new_object_name) |
|
697 self.Table.SetValueByName(new_row, "Object Identifier", new_object_id) |
|
698 self.Table.ResetView(self) |
|
699 return new_row |
|
700 |
|
701 |
|
702 # Called when a object ID is changed |
|
703 # call graph: ObjectEditor.OnVariablesGridCellChange() --> this method |
|
704 # Will check whether there is a duplicate object ID, and highlight it if so. |
|
705 def HighlightDuplicateObjectIDs(self): |
|
706 if self.Table.GetNumberRows() < 2: |
|
707 # Less than 2 rows. No duplicates are possible! |
|
708 return |
|
709 IDsCount = self.Table.GetObjectIDCount() |
|
710 # check ALL object IDs for duplicates... |
|
711 for row in range(self.Table.GetNumberRows()): |
|
712 obj_id1 = int(self.Table.GetValueByName(row, "Object Identifier")) |
|
713 if IDsCount[obj_id1] > 1: |
|
714 # More than 1 BACnet object using this ID! Let us Highlight this row with errors... |
|
715 # TODO: change the hardcoded column number '0' to a number obtained at runtime |
|
716 # that is guaranteed to match the column titled "Object Identifier" |
|
717 self.SetCellBackgroundColour(row, 0, ERROR_HIGHLIGHT[0]) |
|
718 self.SetCellTextColour (row, 0, ERROR_HIGHLIGHT[1]) |
|
719 else: |
|
720 self.SetCellBackgroundColour(row, 0, wx.WHITE) |
|
721 self.SetCellTextColour (row, 0, wx.BLACK) |
|
722 # Refresh the graphical display to take into account any changes we may have made |
|
723 self.ForceRefresh() |
|
724 return None |
|
725 |
|
726 |
|
727 # Called when the user changes the name of BACnet object (using the GUI grid) |
|
728 # call graph: ObjectEditor.OnVariablesGridCellChange() --> |
|
729 # --> BacnetSlaveEditorPlug.HighlightAllDuplicateObjectNames() --> |
|
730 # --> ObjectEditor.HighlightDuplicateObjectNames() --> |
|
731 # --> (this method) |
|
732 # Will check whether there is a duplicate BACnet object name, and highlight it if so. |
|
733 # |
|
734 # Since the names of BACnet objects must be unique within the whole bacnet server (and |
|
735 # not just among the BACnet objects of the same class (e.g. Analog Value, Binary Input, ...) |
|
736 # to work properly this method must be passed a list of the names of all BACnet objects |
|
737 # currently configured. |
|
738 # |
|
739 # AllObjectNamesFreq: a dictionary using as key the names of all currently configured BACnet |
|
740 # objects, and value the number of objects using this same name. |
|
741 def HighlightDuplicateObjectNames(self, AllObjectNamesFreq): |
|
742 for row in range(self.Table.GetNumberRows()): |
|
743 # TODO: change the hardcoded column number '1' to a number obtained at runtime |
|
744 # that is guaranteed to match the column titled "Object Name" |
|
745 if AllObjectNamesFreq[self.Table.GetValueByName(row, "Object Name")] > 1: |
|
746 # This is an error! Highlight it... |
|
747 self.SetCellBackgroundColour(row, 1, ERROR_HIGHLIGHT[0]) |
|
748 self.SetCellTextColour (row, 1, ERROR_HIGHLIGHT[1]) |
|
749 else: |
|
750 self.SetCellBackgroundColour(row, 1, wx.WHITE) |
|
751 self.SetCellTextColour (row, 1, wx.BLACK) |
|
752 # Refresh the graphical display to take into account any changes we may have made |
|
753 self.ForceRefresh() |
|
754 return None |
|
755 |
|
756 |
|
757 |
|
758 |
|
759 class ObjectEditor(wx.Panel): |
|
760 # This ObjectEditor class: |
|
761 # Creates and controls the GUI window for configuring all the BACnet objects of one |
|
762 # (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...) |
|
763 # This 'window' is currenty displayed within one tab of the bacnet plugin, but this |
|
764 # may change in the future! |
|
765 # |
|
766 # It includes a grid to display all the BACnet objects of its type , as well as the buttons |
|
767 # to insert, delete and move (up/down) a BACnet object in the grid. |
|
768 # It also includes the sizers and spacers required to lay out the grid and buttons |
|
769 # in the wndow. |
|
770 # |
|
771 def __init__(self, parent, window, controller, ObjTable): |
|
772 # window: the window in which the editor will open. |
|
773 # controller: The ConfigTreeNode object that controlls the data presented by |
|
774 # this 'config tree node editor' |
|
775 # |
|
776 # parent: wx._controls.Notebook |
|
777 # window: BacnetSlaveEditorPlug (i.e. beremiz.bacnet.BacnetSlaveEditor.BacnetSlaveEditorPlug) |
|
778 # controller: controller will be an object of class |
|
779 # FinalCTNClass (i.e. beremiz.ConfigTreeNode.FinalCTNClass ) |
|
780 # (FinalCTNClass inherits from: ConfigTreeNode and _BacnetSlavePlug) |
|
781 # (For the BACnet plugin, it is easier to think of controller as a _BacnetSlavePlug, |
|
782 # as the other classes are generic to all plugins!!) |
|
783 # |
|
784 # ObjTable: The object of class ObjectTable that stores the data displayed in the grid. |
|
785 # This object is instantiated and managed by the _BacnetSlavePlug class. |
|
786 # |
|
787 self.window = window |
|
788 self.controller = controller |
|
789 self.ObjTable = ObjTable |
|
790 |
|
791 wx.Panel.__init__(self, parent) |
|
792 |
|
793 # The main sizer, 2 rows: top row for buttons, bottom row for 2D grid |
|
794 self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0) |
|
795 self.MainSizer.AddGrowableCol(0) |
|
796 self.MainSizer.AddGrowableRow(1) |
|
797 |
|
798 # sizer placed on top row of main sizer: |
|
799 # 1 row; 6 columns: 1 static text, one stretchable spacer, 4 buttons |
|
800 controls_sizer = wx.FlexGridSizer(cols=6, hgap=4, rows=1, vgap=5) |
|
801 controls_sizer.AddGrowableCol(0) |
|
802 controls_sizer.AddGrowableRow(0) |
|
803 self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW|wx.ALL) |
|
804 |
|
805 # the buttons that populate the controls sizer (itself in top row of the main sizer) |
|
806 # NOTE: the _("string") function will translate the "string" to the local language |
|
807 controls_sizer.Add(wx.StaticText(self, label=_('Object Properties:')), flag=wx.ALIGN_BOTTOM) |
|
808 controls_sizer.AddStretchSpacer() |
|
809 for name, bitmap, help in [ |
|
810 ("AddButton" , "add_element" , _("Add variable" )), |
|
811 ("DeleteButton", "remove_element", _("Remove variable" )), |
|
812 ("UpButton" , "up" , _("Move variable up" )), |
|
813 ("DownButton" , "down" , _("Move variable down"))]: |
|
814 button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), |
|
815 size=wx.Size(28, 28), |
|
816 style=wx.NO_BORDER) |
|
817 button.SetToolTipString(help) |
|
818 setattr(self, name, button) |
|
819 controls_sizer.Add(button) |
|
820 |
|
821 # the variable grid that will populate the bottom row of the main sizer |
|
822 panel = self |
|
823 self.VariablesGrid = ObjectGrid(panel, style=wx.VSCROLL) |
|
824 #self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) # use only to enable drag'n'drop |
|
825 self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange) |
|
826 #self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick) |
|
827 #self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown) |
|
828 self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW) |
|
829 |
|
830 # Configure the Variables Grid... |
|
831 self.VariablesGrid.SetRowLabelSize(0) # do not include a leftmost column containing the 'row label' |
|
832 self.VariablesGrid.SetButtons({"Add": self.AddButton, |
|
833 "Delete": self.DeleteButton, |
|
834 "Up": self.UpButton, |
|
835 "Down": self.DownButton}) |
|
836 # The custom grid needs to know the default values to use when 'AddButton' creates a new row |
|
837 # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes |
|
838 # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject) |
|
839 # which inherit from one of (BinaryObject, AnalogObject, MultiSObject) |
|
840 self.VariablesGrid.SetDefaultValue(self.ObjTable.BACnetObjectType.DefaultValues) |
|
841 |
|
842 # self.ObjTable: The table that contains the data displayed in the grid |
|
843 # This table was instantiated/created in the initializer for class _BacnetSlavePlug |
|
844 self.VariablesGrid.SetTable(self.ObjTable) |
|
845 self.VariablesGrid.SetEditable(True) |
|
846 # set the column attributes (width, alignment) |
|
847 # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes |
|
848 # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject) |
|
849 # which inherit from one of (BinaryObject, AnalogObject, MultiSObject) |
|
850 ColumnAlignments = self.ObjTable.BACnetObjectType.ColumnAlignments |
|
851 ColumnSizes = self.ObjTable.BACnetObjectType.ColumnSizes |
|
852 for col in range( self.ObjTable.GetNumberCols()): |
|
853 attr = wx.grid.GridCellAttr() |
|
854 attr.SetAlignment(ColumnAlignments[col], wx.ALIGN_CENTRE) |
|
855 self.VariablesGrid.SetColAttr(col, attr) |
|
856 self.VariablesGrid.SetColMinimalWidth(col, ColumnSizes[col]) |
|
857 self.VariablesGrid.AutoSizeColumn(col, False) |
|
858 |
|
859 # layout the items in all sizers, and show them too. |
|
860 self.SetSizer(self.MainSizer) # Have the wondow 'own' the sizer... |
|
861 #self.MainSizer.ShowItems(True) # not needed once the window 'owns' the sizer (SetSizer()) |
|
862 #self.MainSizer.Layout() # not needed once the window 'owns' the sizer (SetSizer()) |
|
863 |
|
864 # Refresh the view of the grid... |
|
865 # We ask the table to do that, who in turn will configure the grid for us!! |
|
866 # It will configure the CellRenderers and CellEditors taking into account the type of |
|
867 # BACnet object being shown in the grid!! |
|
868 # |
|
869 # Yes, I (Mario de Sousa) know this architecture does not seem to make much sense. |
|
870 # It seems to me that the cell renderers etc. should all be configured right here. |
|
871 # Unfortunately we inherit from the customTable and customGrid classes in Beremiz |
|
872 # (in order to maintain GUI consistency), so we have to adopt their way of doing things. |
|
873 # |
|
874 # NOTE: ObjectTable.ResetView() (remember, ObjTable is of class ObjectTable) |
|
875 # calls ObjectTable._updateColAttrs(), who will do the configuring. |
|
876 self.ObjTable.ResetView(self.VariablesGrid) |
|
877 |
|
878 |
|
879 def RefreshView(self): |
|
880 #print "ObjectEditor.RefreshView() called!!!" |
|
881 # Check for Duplicate Object IDs is only done within same BACnet object type (ID is unique by type). |
|
882 # The VariablesGrid class can handle it by itself. |
|
883 self.VariablesGrid.HighlightDuplicateObjectIDs() |
|
884 # Check for Duplicate Object Names must be done globally (Object Name is unique within bacnet server) |
|
885 # Only the BacnetSlaveEditorPlug can and will handle this. |
|
886 #self.window.HighlightAllDuplicateObjectNames() |
|
887 pass |
|
888 |
|
889 ######################################### |
|
890 # Event handlers for the Variables Grid # |
|
891 ######################################### |
|
892 def OnVariablesGridCellChange(self, event): |
|
893 row, col = event.GetRow(), event.GetCol() |
|
894 #print "ObjectEditor.OnVariablesGridCellChange(row=%s, col=%s) called!!!" % (row, col) |
|
895 self.ObjTable.ChangesToSave = True |
|
896 if self.ObjTable.GetColLabelValue(col) == "Object Identifier": |
|
897 # an Object ID was changed => must check duplicate object IDs. |
|
898 self.VariablesGrid.HighlightDuplicateObjectIDs() |
|
899 if self.ObjTable.GetColLabelValue(col) == "Object Name": |
|
900 # an Object Name was changed => must check duplicate object names. |
|
901 # Note that this must be done to _all_ BACnet objects, and not just the objects |
|
902 # of the same BACnet class (Binary Value, Analog Input, ...) |
|
903 # So we have the BacnetSlaveEditorPlug class do it... |
|
904 self.window.HighlightAllDuplicateObjectNames() |
|
905 # There are changes to save => |
|
906 # udate the enabled/disabled state of the 'save' option in the 'file' menu |
|
907 self.window.RefreshBeremizWindow() |
|
908 event.Skip() |
|
909 |
|
910 def OnVariablesGridCellLeftClick(self, event): |
|
911 row = event.GetRow() |
|
912 |
|
913 def OnVariablesGridEditorShown(self, event): |
|
914 row, col = event.GetRow(), event.GetCol() |
|
915 |
|
916 def HighlightDuplicateObjectNames(self, AllObjectNamesFreq): |
|
917 return self.VariablesGrid.HighlightDuplicateObjectNames(AllObjectNamesFreq) |
|
918 |
|
919 |
|
920 |
|
921 |
|
922 |
|
923 |
|
924 class BacnetSlaveEditorPlug(ConfTreeNodeEditor): |
|
925 # inheritance tree |
|
926 # wx.SplitterWindow-->EditorPanel-->ConfTreeNodeEditor-->BacnetSlaveEditorPlug |
|
927 # |
|
928 # self.Controller -> The object that controls the data displayed in this editor |
|
929 # In our case, the object of class _BacnetSlavePlug |
|
930 |
|
931 CONFNODEEDITOR_TABS = [ |
|
932 (_("Analog Value Objects"), "_create_AV_ObjectEditor"), |
|
933 (_("Analog Output Objects"), "_create_AO_ObjectEditor"), |
|
934 (_("Analog Input Objects"), "_create_AI_ObjectEditor"), |
|
935 (_("Binary Value Objects"), "_create_BV_ObjectEditor"), |
|
936 (_("Binary Output Objects"), "_create_BO_ObjectEditor"), |
|
937 (_("Binary Input Objects"), "_create_BI_ObjectEditor"), |
|
938 (_("Multi-State Value Objects"), "_create_MSV_ObjectEditor"), |
|
939 (_("Multi-State Output Objects"), "_create_MSO_ObjectEditor"), |
|
940 (_("Multi-State Input Objects"), "_create_MSI_ObjectEditor")] |
|
941 |
|
942 def _create_AV_ObjectEditor(self, parent): |
|
943 self.AV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AV_Obj"]) |
|
944 return self.AV_ObjectEditor |
|
945 |
|
946 def _create_AO_ObjectEditor(self, parent): |
|
947 self.AO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AO_Obj"]) |
|
948 return self.AO_ObjectEditor |
|
949 |
|
950 def _create_AI_ObjectEditor(self, parent): |
|
951 self.AI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AI_Obj"]) |
|
952 return self.AI_ObjectEditor |
|
953 |
|
954 def _create_BV_ObjectEditor(self, parent): |
|
955 self.BV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BV_Obj"]) |
|
956 return self.BV_ObjectEditor |
|
957 |
|
958 def _create_BO_ObjectEditor(self, parent): |
|
959 self.BO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BO_Obj"]) |
|
960 return self.BO_ObjectEditor |
|
961 |
|
962 def _create_BI_ObjectEditor(self, parent): |
|
963 self.BI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BI_Obj"]) |
|
964 return self.BI_ObjectEditor |
|
965 |
|
966 def _create_MSV_ObjectEditor(self, parent): |
|
967 self.MSV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSV_Obj"]) |
|
968 return self.MSV_ObjectEditor |
|
969 |
|
970 def _create_MSO_ObjectEditor(self, parent): |
|
971 self.MSO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSO_Obj"]) |
|
972 return self.MSO_ObjectEditor |
|
973 |
|
974 def _create_MSI_ObjectEditor(self, parent): |
|
975 self.MSI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSI_Obj"]) |
|
976 return self.MSI_ObjectEditor |
|
977 |
|
978 def __init__(self, parent, controler, window, editable=True): |
|
979 self.Editable = editable |
|
980 ConfTreeNodeEditor.__init__(self, parent, controler, window) |
|
981 |
|
982 def __del__(self): |
|
983 self.Controler.OnCloseEditor(self) |
|
984 |
|
985 def GetConfNodeMenuItems(self): |
|
986 return [] |
|
987 |
|
988 def RefreshConfNodeMenu(self, confnode_menu): |
|
989 return |
|
990 |
|
991 def RefreshView(self): |
|
992 self.HighlightAllDuplicateObjectNames() |
|
993 ConfTreeNodeEditor.RefreshView(self) |
|
994 self. AV_ObjectEditor.RefreshView() |
|
995 self. AO_ObjectEditor.RefreshView() |
|
996 self. AI_ObjectEditor.RefreshView() |
|
997 self. BV_ObjectEditor.RefreshView() |
|
998 self. BO_ObjectEditor.RefreshView() |
|
999 self. BI_ObjectEditor.RefreshView() |
|
1000 self.MSV_ObjectEditor.RefreshView() |
|
1001 self.MSO_ObjectEditor.RefreshView() |
|
1002 self.MSI_ObjectEditor.RefreshView() |
|
1003 |
|
1004 def HighlightAllDuplicateObjectNames(self): |
|
1005 ObjectNamesCount = self.Controler.GetObjectNamesCount() |
|
1006 self. AV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1007 self. AO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1008 self. AI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1009 self. BV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1010 self. BO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1011 self. BI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1012 self.MSV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1013 self.MSO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1014 self.MSI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount) |
|
1015 return None |
|
1016 |
|
1017 |
|
1018 def RefreshBeremizWindow(self): |
|
1019 # self.ParentWindow is the top level Beremiz class (object) that |
|
1020 # handles the beremiz window and layout |
|
1021 self.ParentWindow.RefreshTitle() # Refresh the title of the Beremiz window |
|
1022 # (it changes depending on whether there are |
|
1023 # changes to save!! ) |
|
1024 self.ParentWindow.RefreshFileMenu() # Refresh the enabled/disabled state of the |
|
1025 # entries in the main 'file' menu. |
|
1026 # ('Save' sub-menu should become enabled |
|
1027 # if there are changes to save! ) |
|
1028 ###self.ParentWindow.RefreshEditMenu() |
|
1029 ###self.ParentWindow.RefreshPageTitles() |