bacnet/BacnetSlaveEditor.py
changeset 2020 6dddf3070806
child 2250 86f61c4dfe76
equal deleted inserted replaced
2019:92f02bb17c7e 2020:6dddf3070806
       
     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()