Add BACnet extension from Mario de Sousa <msousa@fe.up.pt>
authorEdouard Tisserant
Fri, 08 Jun 2018 13:28:00 +0200
changeset 2020 6dddf3070806
parent 2019 92f02bb17c7e
child 2021 bcf346f558bd
child 2187 c6321473fac1
Add BACnet extension from Mario de Sousa <msousa@fe.up.pt>
bacnet/BacnetSlaveEditor.py
bacnet/__init__.py
bacnet/bacnet.py
bacnet/ede_files/template_EDE.csv
bacnet/ede_files/template_ObjTypes.csv
bacnet/ede_files/template_StateTexts.csv
bacnet/ede_files/template_Units.csv
bacnet/runtime/ai.c
bacnet/runtime/ai.h
bacnet/runtime/ao.c
bacnet/runtime/ao.h
bacnet/runtime/av.c
bacnet/runtime/av.h
bacnet/runtime/bi.c
bacnet/runtime/bi.h
bacnet/runtime/bo.c
bacnet/runtime/bo.h
bacnet/runtime/bv.c
bacnet/runtime/bv.h
bacnet/runtime/config_bacnet_for_beremiz.h
bacnet/runtime/device.c
bacnet/runtime/device.h
bacnet/runtime/msi.c
bacnet/runtime/msi.h
bacnet/runtime/mso.c
bacnet/runtime/mso.h
bacnet/runtime/msv.c
bacnet/runtime/msv.h
bacnet/runtime/server.c
bacnet/runtime/server.h
features.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/BacnetSlaveEditor.py	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,1029 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard.
+# This files implements the bacnet plugin for Beremiz, adding BACnet server support.
+#
+# Copyright (C) 2017: Mario de Sousa (msousa@fe.up.pt)
+#
+# See COPYING file for copyrights details.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+import wx
+from collections        import Counter
+from pickle             import dump
+from util.BitmapLibrary import GetBitmap
+
+
+
+# Import some libraries on Beremiz code...
+from controls.CustomGrid        import CustomGrid
+from controls.CustomTable       import CustomTable
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from graphics.GraphicCommons    import ERROR_HIGHLIGHT
+
+
+
+# BACnet Engineering units taken from: ASHRAE 135-2016, clause/chapter 21
+BACnetEngineeringUnits = [ 
+    ('(Acceleration) meters-per-second-per-second (166)',              166   ),
+    ('(Area) square-meters (0)',                                       0     ),
+    ('(Area) square-centimeters (116)',                                116   ),
+    ('(Area) square-feet (1)',                                         1     ),
+    ('(Area) square-inches (115)',                                     115   ),
+    ('(Currency) currency1 (105)',                                     105   ),
+    ('(Currency) currency2 (106)',                                     106   ),
+    ('(Currency) currency3 (107)',                                     107   ),
+    ('(Currency) currency4 (108)',                                     108   ),
+    ('(Currency) currency5 (109)',                                     109   ),
+    ('(Currency) currency6 (110)',                                     110   ),
+    ('(Currency) currency7 (111)',                                     111   ),
+    ('(Currency) currency8 (112)',                                     112   ),
+    ('(Currency) currency9 (113)',                                     113   ),
+    ('(Currency) currency10 (114)',                                    114   ),
+    ('(Electrical) milliamperes (2)',                                  2     ),
+    ('(Electrical) amperes (3)',                                       3     ),
+    ('(Electrical) amperes-per-meter (167)',                           167   ),
+    ('(Electrical) amperes-per-square-meter (168)',                    168   ),
+    ('(Electrical) ampere-square-meters (169)',                        169   ),
+    ('(Electrical) decibels (199)',                                    199   ),
+    ('(Electrical) decibels-millivolt (200)',                          200   ),
+    ('(Electrical) decibels-volt (201)',                               201   ),
+    ('(Electrical) farads (170)',                                      170   ),
+    ('(Electrical) henrys (171)',                                      171   ),
+    ('(Electrical) ohms (4)',                                          4     ),
+    ('(Electrical) ohm-meter-squared-per-meter (237)',                 237   ),
+    ('(Electrical) ohm-meters (172)',                                  172   ),
+    ('(Electrical) milliohms (145)',                                   145   ),
+    ('(Electrical) kilohms (122)',                                     122   ),
+    ('(Electrical) megohms (123)',                                     123   ),
+    ('(Electrical) microsiemens (190)',                                190   ),
+    ('(Electrical) millisiemens (202)',                                202   ),
+    ('(Electrical) siemens (173)',                                     173   ),
+    ('(Electrical) siemens-per-meter (174)',                           174   ),
+    ('(Electrical) teslas (175)',                                      175   ),
+    ('(Electrical) volts (5)',                                         5     ),
+    ('(Electrical) millivolts (124)',                                  124   ),
+    ('(Electrical) kilovolts (6)',                                     6     ),
+    ('(Electrical) megavolts (7)',                                     7     ),
+    ('(Electrical) volt-amperes (8)',                                  8     ),
+    ('(Electrical) kilovolt-amperes (9)',                              9     ),
+    ('(Electrical) megavolt-amperes (10)',                             10    ),
+    ('(Electrical) volt-amperes-reactive (11)',                        11    ),
+    ('(Electrical) kilovolt-amperes-reactive (12)',                    12    ),
+    ('(Electrical) megavolt-amperes-reactive (13)',                    13    ),
+    ('(Electrical) volts-per-degree-kelvin (176)',                     176   ),
+    ('(Electrical) volts-per-meter (177)',                             177   ),
+    ('(Electrical) degrees-phase (14)',                                14    ),
+    ('(Electrical) power-factor (15)',                                 15    ),
+    ('(Electrical) webers (178)',                                      178   ),
+    ('(Energy) ampere-seconds (238)',                                  238   ),
+    ('(Energy) volt-ampere-hours (239)',                               239   ),
+    ('(Energy) kilovolt-ampere-hours (240)',                           240   ),
+    ('(Energy) megavolt-ampere-hours (241)',                           241   ),
+    ('(Energy) volt-ampere-hours-reactive (242)',                      242   ),
+    ('(Energy) kilovolt-ampere-hours-reactive (243)',                  243   ),
+    ('(Energy) megavolt-ampere-hours-reactive (244)',                  244   ),
+    ('(Energy) volt-square-hours (245)',                               245   ),
+    ('(Energy) ampere-square-hours (246)',                             246   ),
+    ('(Energy) joules (16)',                                           16    ),
+    ('(Energy) kilojoules (17)',                                       17    ),
+    ('(Energy) kilojoules-per-kilogram (125)',                         125   ),
+    ('(Energy) megajoules (126)',                                      126   ),
+    ('(Energy) watt-hours (18)',                                       18    ),
+    ('(Energy) kilowatt-hours (19)',                                   19    ),
+    ('(Energy) megawatt-hours (146)',                                  146   ),
+    ('(Energy) watt-hours-reactive (203)',                             203   ),
+    ('(Energy) kilowatt-hours-reactive (204)',                         204   ),
+    ('(Energy) megawatt-hours-reactive (205)',                         205   ),
+    ('(Energy) btus (20)',                                             20    ),
+    ('(Energy) kilo-btus (147)',                                       147   ),
+    ('(Energy) mega-btus (148)',                                       148   ),
+    ('(Energy) therms (21)',                                           21    ),
+    ('(Energy) ton-hours (22)',                                        22    ),
+    ('(Enthalpy) joules-per-kilogram-dry-air (23)',                    23    ),
+    ('(Enthalpy) kilojoules-per-kilogram-dry-air (149)',               149   ),
+    ('(Enthalpy) megajoules-per-kilogram-dry-air (150)',               150   ),
+    ('(Enthalpy) btus-per-pound-dry-air (24)',                         24    ),
+    ('(Enthalpy) btus-per-pound (117)',                                117   ),
+    ('(Entropy) joules-per-degree-kelvin (127)',                       127   ),
+    ('(Entropy) kilojoules-per-degree-kelvin (151)',                   151   ),
+    ('(Entropy) megajoules-per-degree-kelvin (152)',                   152   ),
+    ('(Entropy) joules-per-kilogram-degree-kelvin (128)',              128   ),
+    ('(Force) newton (153)',                                           153   ),
+    ('(Frequency) cycles-per-hour (25)',                               25    ),
+    ('(Frequency) cycles-per-minute (26)',                             26    ),
+    ('(Frequency) hertz (27)',                                         27    ),
+    ('(Frequency) kilohertz (129)',                                    129   ),
+    ('(Frequency) megahertz (130)',                                    130   ),
+    ('(Frequency) per-hour (131)',                                     131   ),
+    ('(Humidity) grams-of-water-per-kilogram-dry-air (28)',            28    ),
+    ('(Humidity) percent-relative-humidity (29)',                      29    ),
+    ('(Length) micrometers (194)',                                     194   ),
+    ('(Length) millimeters (30)',                                      30    ),
+    ('(Length) centimeters (118)',                                     118   ),
+    ('(Length) kilometers (193)',                                      193   ),
+    ('(Length) meters (31)',                                           31    ),
+    ('(Length) inches (32)',                                           32    ),
+    ('(Length) feet (33)',                                             33    ),
+    ('(Light) candelas (179)',                                         179   ),
+    ('(Light) candelas-per-square-meter (180)',                        180   ),
+    ('(Light) watts-per-square-foot (34)',                             34    ),
+    ('(Light) watts-per-square-meter (35)',                            35    ),
+    ('(Light) lumens (36)',                                            36    ),
+    ('(Light) luxes (37)',                                             37    ),
+    ('(Light) foot-candles (38)',                                      38    ),
+    ('(Mass) milligrams (196)',                                        196   ),
+    ('(Mass) grams (195)',                                             195   ),
+    ('(Mass) kilograms (39)',                                          39    ),
+    ('(Mass) pounds-mass (40)',                                        40    ),
+    ('(Mass) tons (41)',                                               41    ),
+    ('(Mass Flow) grams-per-second (154)',                             154   ),
+    ('(Mass Flow) grams-per-minute (155)',                             155   ),
+    ('(Mass Flow) kilograms-per-second (42)',                          42    ),
+    ('(Mass Flow) kilograms-per-minute (43)',                          43    ),
+    ('(Mass Flow) kilograms-per-hour (44)',                            44    ),
+    ('(Mass Flow) pounds-mass-per-second (119)',                       119   ),
+    ('(Mass Flow) pounds-mass-per-minute (45)',                        45    ),
+    ('(Mass Flow) pounds-mass-per-hour (46)',                          46    ),
+    ('(Mass Flow) tons-per-hour (156)',                                156   ),
+    ('(Power) milliwatts (132)',                                       132   ),
+    ('(Power) watts (47)',                                             47    ),
+    ('(Power) kilowatts (48)',                                         48    ),
+    ('(Power) megawatts (49)',                                         49    ),
+    ('(Power) btus-per-hour (50)',                                     50    ),
+    ('(Power) kilo-btus-per-hour (157)',                               157   ),
+    ('(Power) joule-per-hours (247)',                                  247   ),
+    ('(Power) horsepower (51)',                                        51    ),
+    ('(Power) tons-refrigeration (52)',                                52    ),
+    ('(Pressure) pascals (53)',                                        53    ),
+    ('(Pressure) hectopascals (133)',                                  133   ),
+    ('(Pressure) kilopascals (54)',                                    54    ),
+    ('(Pressure) millibars (134)',                                     134   ),
+    ('(Pressure) bars (55)',                                           55    ),
+    ('(Pressure) pounds-force-per-square-inch (56)',                   56    ),
+    ('(Pressure) millimeters-of-water (206)',                          206   ),
+    ('(Pressure) centimeters-of-water (57)',                           57    ),
+    ('(Pressure) inches-of-water (58)',                                58    ),
+    ('(Pressure) millimeters-of-mercury (59)',                         59    ),
+    ('(Pressure) centimeters-of-mercury (60)',                         60    ),
+    ('(Pressure) inches-of-mercury (61)',                              61    ),
+    ('(Temperature) degrees-celsius (62)',                             62    ),
+    ('(Temperature) degrees-kelvin (63)',                              63    ),
+    ('(Temperature) degrees-kelvin-per-hour (181)',                    181   ),
+    ('(Temperature) degrees-kelvin-per-minute (182)',                  182   ),
+    ('(Temperature) degrees-fahrenheit (64)',                          64    ),
+    ('(Temperature) degree-days-celsius (65)',                         65    ),
+    ('(Temperature) degree-days-fahrenheit (66)',                      66    ),
+    ('(Temperature) delta-degrees-fahrenheit (120)',                   120   ),
+    ('(Temperature) delta-degrees-kelvin (121)',                       121   ),
+    ('(Time) years (67)',                                              67    ),
+    ('(Time) months (68)',                                             68    ),
+    ('(Time) weeks (69)',                                              69    ),
+    ('(Time) days (70)',                                               70    ),
+    ('(Time) hours (71)',                                              71    ),
+    ('(Time) minutes (72)',                                            72    ),
+    ('(Time) seconds (73)',                                            73    ),
+    ('(Time) hundredths-seconds (158)',                                158   ),
+    ('(Time) milliseconds (159)',                                      159   ),
+    ('(Torque) newton-meters (160)',                                   160   ),
+    ('(Velocity) millimeters-per-second (161)',                        161   ),
+    ('(Velocity) millimeters-per-minute (162)',                        162   ),
+    ('(Velocity) meters-per-second (74)',                              74    ),
+    ('(Velocity) meters-per-minute (163)',                             163   ),
+    ('(Velocity) meters-per-hour (164)',                               164   ), 
+    ('(Velocity) kilometers-per-hour (75)',                            75    ), 
+    ('(Velocity) feet-per-second (76)',                                76    ), 
+    ('(Velocity) feet-per-minute (77)',                                77    ), 
+    ('(Velocity) miles-per-hour (78)',                                 78    ), 
+    ('(Volume) cubic-feet (79)',                                       79    ), 
+    ('(Volume) cubic-meters (80)',                                     80    ), 
+    ('(Volume) imperial-gallons (81)',                                 81    ), 
+    ('(Volume) milliliters (197)',                                     197   ), 
+    ('(Volume) liters (82)',                                           82    ), 
+    ('(Volume) us-gallons (83)',                                       83    ), 
+    ('(Volumetric Flow) cubic-feet-per-second (142)',                  142   ), 
+    ('(Volumetric Flow) cubic-feet-per-minute (84)',                   84    ), 
+    ('(Volumetric Flow) million-standard-cubic-feet-per-minute (254)', 254   ), 
+    ('(Volumetric Flow) cubic-feet-per-hour (191)',                    191   ), 
+    ('(Volumetric Flow) cubic-feet-per-day (248)',                     248   ), 
+    ('(Volumetric Flow) standard-cubic-feet-per-day (47808)',          47808 ), 
+    ('(Volumetric Flow) million-standard-cubic-feet-per-day (47809)',  47809 ), 
+    ('(Volumetric Flow) thousand-cubic-feet-per-day (47810)',          47810 ), 
+    ('(Volumetric Flow) thousand-standard-cubic-feet-per-day (47811)', 47811 ), 
+    ('(Volumetric Flow) pounds-mass-per-day (47812)',                  47812 ), 
+    ('(Volumetric Flow) cubic-meters-per-second (85)',                 85    ), 
+    ('(Volumetric Flow) cubic-meters-per-minute (165)',                165   ), 
+    ('(Volumetric Flow) cubic-meters-per-hour (135)',                  135   ), 
+    ('(Volumetric Flow) cubic-meters-per-day (249)',                   249   ), 
+    ('(Volumetric Flow) imperial-gallons-per-minute (86)',             86    ), 
+    ('(Volumetric Flow) milliliters-per-second (198)',                 198   ), 
+    ('(Volumetric Flow) liters-per-second (87)',                       87    ), 
+    ('(Volumetric Flow) liters-per-minute (88)',                       88    ), 
+    ('(Volumetric Flow) liters-per-hour (136)',                        136   ), 
+    ('(Volumetric Flow) us-gallons-per-minute (89)',                   89    ), 
+    ('(Volumetric Flow) us-gallons-per-hour (192)',                    192   ), 
+    ('(Other) degrees-angular (90)',                                   90    ), 
+    ('(Other) degrees-celsius-per-hour (91)',                          91    ), 
+    ('(Other) degrees-celsius-per-minute (92)',                        92    ), 
+    ('(Other) degrees-fahrenheit-per-hour (93)',                       93    ), 
+    ('(Other) degrees-fahrenheit-per-minute (94)',                     94    ), 
+    ('(Other) joule-seconds (183)',                                    183   ), 
+    ('(Other) kilograms-per-cubic-meter (186)',                        186   ), 
+    ('(Other) kilowatt-hours-per-square-meter (137)',                  137   ), 
+    ('(Other) kilowatt-hours-per-square-foot (138)',                   138   ), 
+    ('(Other) watt-hours-per-cubic-meter (250)',                       250   ), 
+    ('(Other) joules-per-cubic-meter (251)',                           251   ), 
+    ('(Other) megajoules-per-square-meter (139)',                      139   ), 
+    ('(Other) megajoules-per-square-foot (140)',                       140   ), 
+    ('(Other) mole-percent (252)',                                     252   ), 
+    ('(Other) no-units (95)',                                          95    ), 
+    ('(Other) newton-seconds (187)',                                   187   ), 
+    ('(Other) newtons-per-meter (188)',                                188   ), 
+    ('(Other) parts-per-million (96)',                                 96    ), 
+    ('(Other) parts-per-billion (97)',                                 97    ), 
+    ('(Other) pascal-seconds (253)',                                   253   ), 
+    ('(Other) percent (98)',                                           98    ), 
+    ('(Other) percent-obscuration-per-foot (143)',                     143   ), 
+    ('(Other) percent-obscuration-per-meter (144)',                    144   ), 
+    ('(Other) percent-per-second (99)',                                99    ), 
+    ('(Other) per-minute (100)',                                       100   ), 
+    ('(Other) per-second (101)',                                       101   ), 
+    ('(Other) psi-per-degree-fahrenheit (102)',                        102   ), 
+    ('(Other) radians (103)',                                          103   ), 
+    ('(Other) radians-per-second (184)',                               184   ), 
+    ('(Other) revolutions-per-minute (104)',                           104   ), 
+    ('(Other) square-meters-per-newton (185)',                         185   ), 
+    ('(Other) watts-per-meter-per-degree-kelvin (189)',                189   ), 
+    ('(Other) watts-per-square-meter-degree-kelvin (141)',             141   ), 
+    ('(Other) per-mille (207)',                                        207   ), 
+    ('(Other) grams-per-gram (208)',                                   208   ), 
+    ('(Other) kilograms-per-kilogram (209)',                           209   ), 
+    ('(Other) grams-per-kilogram (210)',                               210   ), 
+    ('(Other) milligrams-per-gram (211)',                              211   ), 
+    ('(Other) milligrams-per-kilogram (212)',                          212   ), 
+    ('(Other) grams-per-milliliter (213)',                             213   ), 
+    ('(Other) grams-per-liter (214)',                                  214   ), 
+    ('(Other) milligrams-per-liter (215)',                             215   ), 
+    ('(Other) micrograms-per-liter (216)',                             216   ), 
+    ('(Other) grams-per-cubic-meter (217)',                            217   ), 
+    ('(Other) milligrams-per-cubic-meter (218)',                       218   ), 
+    ('(Other) micrograms-per-cubic-meter (219)',                       219   ), 
+    ('(Other) nanograms-per-cubic-meter (220)',                        220   ), 
+    ('(Other) grams-per-cubic-centimeter (221)',                       221   ), 
+    ('(Other) becquerels (222)',                                       222   ), 
+    ('(Other) kilobecquerels (223)',                                   223   ), 
+    ('(Other) megabecquerels (224)',                                   224   ), 
+    ('(Other) gray (225)',                                             225   ), 
+    ('(Other) milligray (226)',                                        226   ), 
+    ('(Other) microgray (227)',                                        227   ), 
+    ('(Other) sieverts (228)',                                         228   ), 
+    ('(Other) millisieverts (229)',                                    229   ), 
+    ('(Other) microsieverts (230)',                                    230   ), 
+    ('(Other) microsieverts-per-hour (231)',                           231   ), 
+    ('(Other) millirems (47814)',                                      47814 ), 
+    ('(Other) millirems-per-hour (47815)',                             47815 ), 
+    ('(Other) decibels-a (232)',                                       232   ), 
+    ('(Other) nephelometric-turbidity-unit (233)',                     233   ), 
+    ('(Other) pH (234)',                                               234   ), 
+    ('(Other) grams-per-square-meter (235)',                           235   ), 
+    ('(Other) minutes-per-degree-kelvin (236)',                        236   )  
+    ] # BACnetEngineeringUnits 
+   
+   
+   
+# ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 = 4194303
+#  However, ObjectID 4194303 is not allowed!
+#  4194303 is used as a special value when object Id reference is referencing an undefined object 
+#  (similar to NULL in C)
+BACnetObjectID_MAX = 4194302
+BACnetObjectID_NUL = 4194303
+
+
+
+# A base class
+# what would be a purely virtual class in C++
+class ObjectProperties:
+    # this __init_() function is currently not beeing used!
+    def __init__(self):
+        #nothing to do 
+        return
+
+
+class BinaryObject(ObjectProperties):
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
+    #          Be sure to use these exact names for these BACnet object properties!
+    PropertyNames    = ["Object Identifier", "Object Name", "Description"]
+    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+    ColumnSizes      = [        40         ,      80      ,      80      ]
+    PropertyConfig   = {"Object Identifier": {"GridCellEditor"  : wx.grid.GridCellNumberEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
+                                              "GridCellEditorParam": "0,4194302" 
+                                                                     # syntax for GridCellNumberEditor -> "min,max" 
+                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
+                                             },
+                        "Object Name"      : {"GridCellEditor"  : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer": wx.grid.GridCellStringRenderer},  
+                        "Description"      : {"GridCellEditor"  : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer": wx.grid.GridCellStringRenderer}
+                       }
+    
+class AnalogObject(ObjectProperties):
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
+    #          Be sure to use these exact names for these BACnet object properties!
+    #
+    # NOTE: Although it is not listed here (so it does not show up in the GUI, this object will also
+    #       keep another entry for a virtual property named "Unit ID". This virtual property
+    #       will store the ID corresponding to the "Engineering Units" currently chosen.
+    #       This virtual property is kept synchronised to the "Engineering Units" property
+    #       by the function PropertyChanged() which should be called by the OnCellChange event handler.
+    PropertyNames    = ["Object Identifier", "Object Name", "Description", "Engineering Units"] #'Unit ID'
+    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT,    wx.ALIGN_LEFT   ]
+    ColumnSizes      = [        40         ,      80      ,      80      ,         200        ]
+    PropertyConfig   = {"Object Identifier": {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
+                                              "GridCellEditorParam": "0,4194302"
+                                                                     # syntax for GridCellNumberEditor -> "min,max" 
+                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
+                                             },
+                        "Object Name"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
+                        "Description"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
+                        "Engineering Units": {"GridCellEditor"     : wx.grid.GridCellChoiceEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer,  
+                                                                     # use string renderer with choice editor!
+                                              "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits]) 
+                                                                     # syntax for GridCellChoiceEditor -> comma separated values 
+                                              }  
+                       }
+
+    # obj_properties should be a dictionary, with keys "Object Identifier", "Object Name", "Description", ...
+    def UpdateVirtualProperties(self, obj_properties):
+        obj_properties["Unit ID"] = [x[1] for x in BACnetEngineeringUnits if x[0] == obj_properties["Engineering Units"]][0]
+
+         
+
+class MultiSObject(ObjectProperties):
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
+    #          Be sure to use these exact names for these BACnet object properties!
+    PropertyNames    = ["Object Identifier", "Object Name", "Description", "Number of States"]
+    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT,   wx.ALIGN_CENTER ]
+    ColumnSizes      = [        40         ,      80      ,      80      ,         120       ]
+    PropertyConfig   = {"Object Identifier": {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
+                                              "GridCellEditorParam": "0,4194302"
+                                                                     # syntax for GridCellNumberEditor -> "min,max" 
+                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
+                                             },
+                        "Object Name"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
+                        "Description"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
+                        "Number of States" : {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
+                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
+                                              "GridCellEditorParam": "1,255" # syntax for GridCellNumberEditor -> "min,max" 
+                                                                             # MultiState Values are encoded in unsigned integer
+                                                                             # (in BACnet => uint8_t), and can not be 0.
+                                                                             # See ASHRAE 135-2016, section 12.20.4
+                                             }
+                       }
+
+
+
+# The default values to use for each BACnet object type
+#
+# Note that the 'internal plugin parameters' get stored in the data table, but
+# are not visible in the GUI. They are used to generate the 
+# EDE files as well as the C code
+class BVObject(BinaryObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Binary Value", 
+                        "Description"      :"",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :5,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"Y"
+                        }
+
+class BOObject(BinaryObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Binary Output", 
+                        "Description"      :"",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :4,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"Y"
+                        }
+
+class BIObject(BinaryObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Binary Input", 
+                        "Description"      :"",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :3,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"N"
+                        }
+
+class AVObject(AnalogObject):
+    DefaultValues    = {"Object Identifier":"", 
+                        "Object Name"      :"Analog Value",
+                        "Description"      :"", 
+                        "Engineering Units":'(Other) no-units (95)',
+                        # internal plugin parameters...
+                        "Unit ID"          :95,   # the ID of the engineering unit
+                                                  # will get updated by UpdateVirtualProperties()
+                        "BACnetObjTypeID"  :2,
+                        "Ctype"            :"float",
+                        "Settable"         :"Y"
+                        }
+
+class AOObject(AnalogObject):
+    DefaultValues    = {"Object Identifier":"", 
+                        "Object Name"      :"Analog Output",
+                        "Description"      :"", 
+                        "Engineering Units":'(Other) no-units (95)',
+                        # internal plugin parameters...
+                        "Unit ID"          :95,   # the ID of the engineering unit
+                                                  # will get updated by UpdateVirtualProperties()
+                        "BACnetObjTypeID"  :1,
+                        "Ctype"            :"float",
+                        "Settable"         :"Y"
+                        }
+
+class AIObject(AnalogObject):
+    DefaultValues    = {"Object Identifier":"", 
+                        "Object Name"      :"Analog Input",
+                        "Description"      :"", 
+                        "Engineering Units":'(Other) no-units (95)',
+                        # internal plugin parameters...
+                        "Unit ID"          :95,   # the ID of the engineering unit
+                                                  # will get updated by UpdateVirtualProperties()
+                        "BACnetObjTypeID"  :0,
+                        "Ctype"            :"float",
+                        "Settable"         :"N"
+                        }
+
+class MSVObject(MultiSObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Multi-state Value",
+                        "Description"      :"",
+                        "Number of States" :"255",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :19,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"Y"
+                        }
+
+class MSOObject(MultiSObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Multi-state Output",
+                        "Description"      :"",
+                        "Number of States" :"255",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :14,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"Y"
+                        }
+
+class MSIObject(MultiSObject):
+    DefaultValues    = {"Object Identifier":"",
+                        "Object Name"      :"Multi-state Input",
+                        "Description"      :"",
+                        "Number of States" :"255",
+                        # internal plugin parameters...
+                        "BACnetObjTypeID"  :13,
+                        "Ctype"            :"uint8_t",
+                        "Settable"         :"N"
+                        }
+
+
+
+
+
+
+
+
+
+
+class ObjectTable(CustomTable):
+    #  A custom wx.grid.PyGridTableBase using user supplied data
+    #  
+    #  This will basically store a list of BACnet objects that the slave will support/implement. 
+    #  There will be one instance of this ObjectTable class for each BACnet object type 
+    #  (e.g. Binary Value, Analog Input, Multi State Output, ...)
+    #  
+    #  The list of BACnet objects will actually be stored within the self.data variable
+    #  (declared in CustomTable). Self.data will be a list of dictionaries (one entry per BACnet
+    #  object). All of these dictionaries in the self.data list will have entries whose keys actually
+    #  depend on the BACnet type object being handled. The keys in the dictionary will be 
+    #  the entries in the PropertyNames list of one of the following classes:
+    #  (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
+    #
+    #  For example, when handling Binary Value BACnet objects, 
+    #   self.data will be a list of dictionaries (one entry per row)
+    #     self.data[n] will be a dictionary, with keys "Object Identifier", "Object Name", "Description"
+    #     for example: self.data[n] = {"Object Identifier":33, "Object Name":"room1", "Description":"xx"} 
+    #  
+    #  Note that this ObjectTable class merely stores the configuration data.
+    #  It does not control the display nor the editing of this data. 
+    #  This data is typically displayed within a grid, that is controlled by the ObjectGrid class.
+    #
+    def __init__(self, parent, data, BACnetObjectType):
+        #   parent          : the _BacnetSlavePlug object that is instantiating this ObjectTable
+        #   data            : a list with the data to be shown on the grid
+        #                       (i.e., a list containing the BACnet object properties)
+        #                       Instantiated in _BacnetSlavePlug
+        #   BACnetObjectType: one of BinaryObject, AnalogObject, MultiSObject
+        #                     (or a class that derives from them).
+        #                     This is actually the class itself, and not a variable!!!
+        #                     However, self.BACnetObjectType will be an instance
+        #                     of said class as we later need to call methods from this class.
+        #                     (in particular, the UpdateVirtualProperties() method)
+        #
+        # The base class must be initialized *first*
+        CustomTable.__init__(self, parent, data, BACnetObjectType.PropertyNames)
+        self.BACnetObjectType = BACnetObjectType()
+        self.ChangesToSave    = False
+
+    #def _GetRowEdit(self, row):
+        #row_edit = self.GetValueByName(row, "Edit")
+        #var_type = self.Parent.GetTagName()
+        #bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type)
+        #if bodytype in ["ST", "IL"]:
+            #row_edit = True;
+        #return row_edit
+
+    def _updateColAttrs(self, grid):
+        #  wx.grid.Grid -> update the column attributes to add the
+        #  appropriate renderer given the column name.
+        #  
+        #  Otherwise default to the default renderer.
+        #print "ObjectTable._updateColAttrs() called!!!"
+        for row in range(self.GetNumberRows()):
+            for col in range(self.GetNumberCols()):
+                PropertyName   = self.BACnetObjectType.PropertyNames[col]
+                PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName]
+                grid.SetReadOnly            (row, col, False)
+                grid.SetCellEditor          (row, col, PropertyConfig["GridCellEditor"]  ())
+                grid.SetCellRenderer        (row, col, PropertyConfig["GridCellRenderer"]())
+                grid.SetCellBackgroundColour(row, col, wx.WHITE)
+                grid.SetCellTextColour      (row, col, wx.BLACK)
+                if "GridCellEditorParam" in PropertyConfig:
+                    grid.GetCellEditor(row, col).SetParameters(PropertyConfig["GridCellEditorParam"])
+            self.ResizeRow(grid, row)
+
+    def FindValueByName(self, PropertyName, PropertyValue):
+        # find the row whose property named PropertyName has the value PropertyValue
+        # Returns: row number
+        # for example, find the row where PropertyName "Object Identifier" has value 1002
+        #              FindValueByName("Object Identifier", 1002).
+        for row in range(self.GetNumberRows()):
+            if int(self.GetValueByName(row, PropertyName)) == PropertyValue:
+                return row
+        return None
+      
+    # Return a list containing all the values under the column named 'colname'  
+    def GetAllValuesByName(self, colname):
+        values = []
+        for row in range(self.GetNumberRows()):
+            values.append(self.data[row].get(colname))
+        return values
+
+    # Returns a dictionary with:
+    #      keys: IDs    of BACnet objects
+    #     value: number of BACnet objects using this same Id 
+    #            (values larger than 1 indicates an error as BACnet requires unique
+    #             object IDs for objects of the same type)
+    def GetObjectIDCount(self):
+        # The dictionary is built by first creating a list containing the IDs of all BACnet objects
+        ObjectIDs = self.GetAllValuesByName("Object Identifier")
+        ObjectIDs_as_int = [int(x) for x in ObjectIDs] # list of integers instead of strings...
+        # This list is then transformed into a collections.Counter class
+        # Which is then transformed into a dictionary using dict()
+        return dict(Counter(ObjectIDs_as_int))
+
+    # Check whether any object ID is used more than once (not valid in BACnet)
+    # (returns True or False)
+    def HasDuplicateObjectIDs(self):
+        ObjectIDsCount = self.GetObjectIDCount()
+        for ObjName in ObjectIDsCount:
+            if ObjectIDsCount[ObjName] > 1:
+                return True
+        return False
+
+    # Update the virtual properties of the objects of the classes derived from ObjectProperties
+    #  (currently only the AnalogObject class had virtua properties, i.e. a property
+    #   that is determined/calculated based on the other properties)
+    def UpdateAllVirtualProperties(self):
+        if hasattr(self.BACnetObjectType, 'UpdateVirtualProperties'):
+            for ObjProp in self.data:
+                self.BACnetObjectType.UpdateVirtualProperties(ObjProp)
+
+
+
+class ObjectGrid(CustomGrid):
+    # A custom wx.grid.Grid (CustomGrid derives from wx.grid.Grid)
+    # 
+    # Needed mostly to customise the initial values of newly added rows, and to 
+    # validate if the inserted data follows BACnet rules.
+    #
+    #
+    # This ObjectGrid class:
+    #   Creates and controls the GUI __grid__ for configuring all the BACnet objects of one
+    #   (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...)
+    #   This grid is currently displayed within one 'window' controlled by a ObjectEditor
+    #   object (this organization is not likely to change in the future).
+    #
+    #   The grid uses one line/row per BACnet object, and one column for each property of the BACnet
+    #   object. The column titles change depending on the specific type of BACnet object being edited
+    #   (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
+    #   The editor to use for each column is also obtained from that class (e.g. TextEditor, 
+    #   NumberEditor, ...)
+    #
+    #   This class does NOT store the data in the grid. It merely controls its display and editing.
+    #   The data in the grid is stored within an object of class ObjectTable
+    #
+    def __init__(self, *args, **kwargs):
+        CustomGrid.__init__(self, *args, **kwargs)
+
+    # Called when a new row is added by clicking Add button
+    #    call graph: CustomGrid.OnAddButton() --> CustomGrid.AddRow() --> ObjectGrid._AddRow()
+    def _AddRow(self, new_row):
+        if new_row > 0:
+            self.Table.InsertRow(new_row, self.Table.GetRow(new_row - 1).copy())
+        else:
+            self.Table.InsertRow(new_row, self.DefaultValue.copy())
+        self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL) # start off with invalid object ID
+        # Find an apropriate BACnet object ID for the new object.
+        # We choose a first attempt (based on object ID of previous line + 1)
+        new_object_id = 0
+        if new_row > 0:
+            new_object_id = int(self.Table.GetValueByName(new_row - 1, "Object Identifier"))
+            new_object_id += 1
+        # Check whether the chosen object ID is not already in use. 
+        # If in use, add 1 to the attempted object ID and recheck...
+        while self.Table.FindValueByName("Object Identifier", new_object_id) is not None:
+            new_object_id += 1
+            # if reached end of object IDs, cycle back to 0 
+            # (remember, we may have started at any inital object ID > 0, so it makes sense to cyclce back to 0)
+            # warning: We risk entering an inifinite loop if all object IDs are already used. 
+            #          The likelyhood of this happening is extremely low, (we would need 2^22 elements in the table!)
+            #          so don't worry about it for now.
+            if new_object_id > BACnetObjectID_MAX:
+                new_object_id = 0
+        # Set the object ID of the new object to the apropriate value
+        # ... and append the ID to the default object name (so the name becomes unique)
+        new_object_name = self.DefaultValue.get("Object Name") + " " + str(new_object_id)
+        self.Table.SetValueByName(new_row, "Object Name"      , new_object_name)        
+        self.Table.SetValueByName(new_row, "Object Identifier", new_object_id)
+        self.Table.ResetView(self)
+        return new_row
+
+
+    # Called when a object ID is changed
+    #    call graph: ObjectEditor.OnVariablesGridCellChange() --> this method
+    # Will check whether there is a duplicate object ID, and highlight it if so.
+    def HighlightDuplicateObjectIDs(self):
+        if self.Table.GetNumberRows() < 2:
+            # Less than 2 rows. No duplicates are possible! 
+            return
+        IDsCount = self.Table.GetObjectIDCount()
+        # check ALL object IDs for duplicates...
+        for row in range(self.Table.GetNumberRows()):
+            obj_id1 = int(self.Table.GetValueByName(row, "Object Identifier"))
+            if IDsCount[obj_id1] > 1:
+                # More than 1 BACnet object using this ID! Let us Highlight this row with errors...            
+                # TODO: change the hardcoded column number '0' to a number obtained at runtime
+                #       that is guaranteed to match the column titled "Object Identifier"
+                self.SetCellBackgroundColour(row, 0, ERROR_HIGHLIGHT[0])
+                self.SetCellTextColour      (row, 0, ERROR_HIGHLIGHT[1])
+            else: 
+                self.SetCellBackgroundColour(row, 0, wx.WHITE)
+                self.SetCellTextColour      (row, 0, wx.BLACK)
+        # Refresh the graphical display to take into account any changes we may have made
+        self.ForceRefresh()
+        return None        
+
+
+    # Called when the user changes the name of BACnet object (using the GUI grid)
+    #    call graph: ObjectEditor.OnVariablesGridCellChange() --> 
+    #                --> BacnetSlaveEditorPlug.HighlightAllDuplicateObjectNames() -->
+    #                --> ObjectEditor.HighlightDuplicateObjectNames() -->
+    #                -->   (this method)
+    # Will check whether there is a duplicate BACnet object name, and highlight it if so.
+    #
+    # Since the names of BACnet objects must be unique within the whole bacnet server (and 
+    # not just among the BACnet objects of the same class (e.g. Analog Value, Binary Input, ...)
+    # to work properly this method must be passed a list of the names of all BACnet objects
+    # currently configured.
+    #
+    # AllObjectNamesFreq: a dictionary using as key the names of all currently configured BACnet
+    #                     objects, and value the number of objects using this same name.
+    def HighlightDuplicateObjectNames(self, AllObjectNamesFreq):
+        for row in range(self.Table.GetNumberRows()):
+            # TODO: change the hardcoded column number '1' to a number obtained at runtime
+            #       that is guaranteed to match the column titled "Object Name"
+            if AllObjectNamesFreq[self.Table.GetValueByName(row, "Object Name")] > 1:
+                # This is an error! Highlight it...            
+                self.SetCellBackgroundColour(row, 1, ERROR_HIGHLIGHT[0])
+                self.SetCellTextColour      (row, 1, ERROR_HIGHLIGHT[1])
+            else: 
+                self.SetCellBackgroundColour(row, 1, wx.WHITE)
+                self.SetCellTextColour      (row, 1, wx.BLACK)
+        # Refresh the graphical display to take into account any changes we may have made
+        self.ForceRefresh()
+        return None        
+      
+      
+      
+
+class ObjectEditor(wx.Panel):
+    # This ObjectEditor class:
+    #   Creates and controls the GUI window for configuring all the BACnet objects of one
+    #   (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...)
+    #   This 'window' is currenty displayed within one tab of the bacnet plugin, but this
+    #   may change in the future!
+    #
+    #   It includes a grid to display all the BACnet objects of its type , as well as the buttons 
+    #   to insert, delete and move (up/down) a BACnet object in the grid.
+    #   It also includes the sizers and spacers required to lay out the grid and buttons
+    #   in the wndow.
+    #
+    def __init__(self, parent, window, controller, ObjTable):
+        # window:  the window in which the editor will open.
+        # controller: The ConfigTreeNode object that controlls the data presented by 
+        #             this 'config tree node editor'
+        #
+        # parent:  wx._controls.Notebook
+        # window:  BacnetSlaveEditorPlug (i.e. beremiz.bacnet.BacnetSlaveEditor.BacnetSlaveEditorPlug)
+        # controller: controller will be an object of class
+        #            FinalCTNClass         (i.e. beremiz.ConfigTreeNode.FinalCTNClass )
+        #            (FinalCTNClass inherits from: ConfigTreeNode and _BacnetSlavePlug)
+        #            (For the BACnet plugin, it is easier to think of controller as a _BacnetSlavePlug, 
+        #             as the other classes are generic to all plugins!!)
+        #
+        # ObjTable: The object of class ObjectTable that stores the data displayed in the grid.
+        #           This object is instantiated and managed by the _BacnetSlavePlug class.
+        #
+        self.window   = window
+        self.controller  = controller
+        self.ObjTable = ObjTable
+      
+        wx.Panel.__init__(self, parent)
+        
+        # The main sizer, 2 rows: top row for buttons, bottom row for 2D grid
+        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
+        self.MainSizer.AddGrowableCol(0)
+        self.MainSizer.AddGrowableRow(1)
+
+        # sizer placed on top row of main sizer: 
+        #   1 row; 6 columns: 1 static text, one stretchable spacer, 4 buttons
+        controls_sizer = wx.FlexGridSizer(cols=6, hgap=4, rows=1, vgap=5)
+        controls_sizer.AddGrowableCol(0)
+        controls_sizer.AddGrowableRow(0)
+        self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
+
+        # the buttons that populate the controls sizer (itself in top row of the main sizer)
+        # NOTE: the _("string") function will translate the "string" to the local language
+        controls_sizer.Add(wx.StaticText(self, label=_('Object Properties:')), flag=wx.ALIGN_BOTTOM)         
+        controls_sizer.AddStretchSpacer()
+        for name, bitmap, help in [
+                ("AddButton"   , "add_element"   , _("Add variable"      )),
+                ("DeleteButton", "remove_element", _("Remove variable"   )),
+                ("UpButton"    , "up"            , _("Move variable up"  )),
+                ("DownButton"  , "down"          , _("Move variable down"))]:
+            button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+                                                    size=wx.Size(28, 28), 
+                                                    style=wx.NO_BORDER)
+            button.SetToolTipString(help)
+            setattr(self, name, button)
+            controls_sizer.Add(button)         
+
+        # the variable grid that will populate the bottom row of the main sizer 
+        panel = self
+        self.VariablesGrid = ObjectGrid(panel, style=wx.VSCROLL)
+        #self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) # use only to enable drag'n'drop
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,     self.OnVariablesGridCellChange)
+        #self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
+        #self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,    self.OnVariablesGridEditorShown)
+        self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
+ 
+        # Configure the Variables Grid...
+        self.VariablesGrid.SetRowLabelSize(0)  # do not include a leftmost column containing the 'row label'
+        self.VariablesGrid.SetButtons({"Add":    self.AddButton,
+                                       "Delete": self.DeleteButton,
+                                       "Up":     self.UpButton,
+                                       "Down":   self.DownButton})
+        # The custom grid needs to know the default values to use when 'AddButton' creates a new row
+        # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes
+        # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
+        # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
+        self.VariablesGrid.SetDefaultValue(self.ObjTable.BACnetObjectType.DefaultValues)
+
+        # self.ObjTable: The table that contains the data displayed in the grid
+        #  This table was instantiated/created in the initializer for class _BacnetSlavePlug
+        self.VariablesGrid.SetTable(self.ObjTable)
+        self.VariablesGrid.SetEditable(True)
+        # set the column attributes (width, alignment)
+        # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes
+        # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
+        # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
+        ColumnAlignments = self.ObjTable.BACnetObjectType.ColumnAlignments
+        ColumnSizes      = self.ObjTable.BACnetObjectType.ColumnSizes
+        for col in range(  self.ObjTable.GetNumberCols()):
+            attr = wx.grid.GridCellAttr()
+            attr.SetAlignment(ColumnAlignments[col], wx.ALIGN_CENTRE)
+            self.VariablesGrid.SetColAttr(col, attr)
+            self.VariablesGrid.SetColMinimalWidth(col, ColumnSizes[col])
+            self.VariablesGrid.AutoSizeColumn(col, False)
+ 
+        # layout the items in all sizers, and show them too.
+        self.SetSizer(self.MainSizer)    # Have the wondow 'own' the sizer...   
+        #self.MainSizer.ShowItems(True)  # not needed once the window 'owns' the sizer (SetSizer())
+        #self.MainSizer.Layout()         # not needed once the window 'owns' the sizer (SetSizer())
+        
+        # Refresh the view of the grid...
+        #   We ask the table to do that, who in turn will configure the grid for us!!
+        #   It will configure the CellRenderers and CellEditors taking into account the type of
+        #   BACnet object being shown in the grid!!
+        #
+        #   Yes, I (Mario de Sousa) know this architecture does not seem to make much sense.
+        #   It seems to me that the cell renderers etc. should all be configured right here.
+        #   Unfortunately we inherit from the customTable and customGrid classes in Beremiz
+        #   (in order to maintain GUI consistency), so we have to adopt their way of doing things.
+        #
+        # NOTE: ObjectTable.ResetView() (remember, ObjTable is of class ObjectTable)
+        #         calls ObjectTable._updateColAttrs(), who will do the configuring. 
+        self.ObjTable.ResetView(self.VariablesGrid)
+
+
+    def RefreshView(self):
+        #print "ObjectEditor.RefreshView() called!!!"
+        # Check for Duplicate Object IDs is only done within same BACnet object type (ID is unique by type).
+        # The VariablesGrid class can handle it by itself.
+        self.VariablesGrid.HighlightDuplicateObjectIDs()
+        # Check for Duplicate Object Names must be done globally (Object Name is unique within bacnet server)
+        # Only the BacnetSlaveEditorPlug can and will handle this.
+        #self.window.HighlightAllDuplicateObjectNames()
+        pass      
+
+    #########################################
+    # Event handlers for the Variables Grid #
+    #########################################
+    def OnVariablesGridCellChange(self, event):
+        row, col = event.GetRow(), event.GetCol()
+        #print "ObjectEditor.OnVariablesGridCellChange(row=%s, col=%s) called!!!" % (row, col)
+        self.ObjTable.ChangesToSave = True
+        if self.ObjTable.GetColLabelValue(col) == "Object Identifier":
+            # an Object ID was changed => must check duplicate object IDs. 
+            self.VariablesGrid.HighlightDuplicateObjectIDs()
+        if self.ObjTable.GetColLabelValue(col) == "Object Name":
+            # an Object Name was changed => must check duplicate object names. 
+            # Note that this must be done to _all_ BACnet objects, and not just the objects
+            # of the same BACnet class (Binary Value, Analog Input, ...)
+            # So we have the BacnetSlaveEditorPlug class do it...
+            self.window.HighlightAllDuplicateObjectNames()
+        # There are changes to save => 
+        #        udate the enabled/disabled state of the 'save' option in the 'file' menu
+        self.window.RefreshBeremizWindow()
+        event.Skip()
+
+    def OnVariablesGridCellLeftClick(self, event):
+        row = event.GetRow()
+
+    def OnVariablesGridEditorShown(self, event):
+        row, col = event.GetRow(), event.GetCol()
+
+    def HighlightDuplicateObjectNames(self, AllObjectNamesFreq):
+        return self.VariablesGrid.HighlightDuplicateObjectNames(AllObjectNamesFreq)
+
+
+
+
+
+
+class BacnetSlaveEditorPlug(ConfTreeNodeEditor):
+    # inheritance tree
+    #  wx.SplitterWindow-->EditorPanel-->ConfTreeNodeEditor-->BacnetSlaveEditorPlug
+    #
+    # self.Controller -> The object that controls the data displayed in this editor
+    #                    In our case, the object of class _BacnetSlavePlug
+    
+    CONFNODEEDITOR_TABS = [
+        (_("Analog Value Objects"),       "_create_AV_ObjectEditor"),
+        (_("Analog Output Objects"),      "_create_AO_ObjectEditor"),
+        (_("Analog Input Objects"),       "_create_AI_ObjectEditor"),
+        (_("Binary Value Objects"),       "_create_BV_ObjectEditor"),
+        (_("Binary Output Objects"),      "_create_BO_ObjectEditor"),
+        (_("Binary Input Objects"),       "_create_BI_ObjectEditor"),
+        (_("Multi-State Value Objects"),  "_create_MSV_ObjectEditor"),
+        (_("Multi-State Output Objects"), "_create_MSO_ObjectEditor"),
+        (_("Multi-State Input Objects"), "_create_MSI_ObjectEditor")]
+    
+    def _create_AV_ObjectEditor(self, parent):
+        self.AV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AV_Obj"])
+        return self.AV_ObjectEditor
+        
+    def _create_AO_ObjectEditor(self, parent):
+        self.AO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AO_Obj"])
+        return self.AO_ObjectEditor
+        
+    def _create_AI_ObjectEditor(self, parent):
+        self.AI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AI_Obj"])
+        return self.AI_ObjectEditor
+        
+    def _create_BV_ObjectEditor(self, parent):
+        self.BV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BV_Obj"])
+        return self.BV_ObjectEditor
+        
+    def _create_BO_ObjectEditor(self, parent):
+        self.BO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BO_Obj"])
+        return self.BO_ObjectEditor
+        
+    def _create_BI_ObjectEditor(self, parent):
+        self.BI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BI_Obj"])
+        return self.BI_ObjectEditor
+        
+    def _create_MSV_ObjectEditor(self, parent):
+        self.MSV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSV_Obj"])
+        return self.MSV_ObjectEditor
+        
+    def _create_MSO_ObjectEditor(self, parent):
+        self.MSO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSO_Obj"])
+        return self.MSO_ObjectEditor
+        
+    def _create_MSI_ObjectEditor(self, parent):
+        self.MSI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSI_Obj"])
+        return self.MSI_ObjectEditor
+        
+    def __init__(self, parent, controler, window, editable=True):
+        self.Editable = editable
+        ConfTreeNodeEditor.__init__(self, parent, controler, window)
+    
+    def __del__(self):
+        self.Controler.OnCloseEditor(self)
+    
+    def GetConfNodeMenuItems(self):
+        return []
+    
+    def RefreshConfNodeMenu(self, confnode_menu):
+        return
+
+    def RefreshView(self):
+        self.HighlightAllDuplicateObjectNames()
+        ConfTreeNodeEditor.RefreshView(self)
+        self. AV_ObjectEditor.RefreshView()
+        self. AO_ObjectEditor.RefreshView()
+        self. AI_ObjectEditor.RefreshView()
+        self. BV_ObjectEditor.RefreshView()
+        self. BO_ObjectEditor.RefreshView()
+        self. BI_ObjectEditor.RefreshView()
+        self.MSV_ObjectEditor.RefreshView()
+        self.MSO_ObjectEditor.RefreshView()
+        self.MSI_ObjectEditor.RefreshView()
+
+    def HighlightAllDuplicateObjectNames(self):
+        ObjectNamesCount = self.Controler.GetObjectNamesCount()
+        self. AV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self. AO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self. AI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self. BV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self. BO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self. BI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self.MSV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self.MSO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        self.MSI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+        return None
+        
+        
+    def RefreshBeremizWindow(self):
+        # self.ParentWindow is the top level Beremiz class (object) that 
+        #                   handles the beremiz window and layout
+        self.ParentWindow.RefreshTitle()        # Refresh the title of the Beremiz window
+                                                #  (it changes depending on whether there are
+                                                #   changes to save!! )
+        self.ParentWindow.RefreshFileMenu()     # Refresh the enabled/disabled state of the
+                                                #  entries in the main 'file' menu. 
+                                                #  ('Save' sub-menu should become enabled
+                                                #   if there are changes to save! )
+        ###self.ParentWindow.RefreshEditMenu()
+        ###self.ParentWindow.RefreshPageTitles()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/__init__.py	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    
+# This code is made available on the understanding that it will not be
+# used in safety-critical situations without a full and competent review.
+
+
+from bacnet import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/bacnet.py	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,697 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard.
+# This files implements the bacnet plugin for Beremiz, adding BACnet server support.
+#
+# Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    
+# This code is made available on the understanding that it will not be
+# used in safety-critical situations without a full and competent review.
+
+
+
+import os, sys
+from collections import Counter
+from datetime    import datetime
+
+base_folder           = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
+base_folder           = os.path.join(base_folder, "..")
+BacnetPath            = os.path.join(base_folder, "BACnet")
+BacnetLibraryPath     = os.path.join(BacnetPath, "lib")
+BacnetIncludePath     = os.path.join(BacnetPath, "include")
+BacnetIncludePortPath = os.path.join(BacnetPath, "ports")
+BacnetIncludePortPath = os.path.join(BacnetIncludePortPath, "linux")
+
+import wx
+import pickle
+
+from BacnetSlaveEditor import *
+from BacnetSlaveEditor import ObjectProperties
+from ConfigTreeNode    import ConfigTreeNode
+from PLCControler      import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
+
+# Parameters to be monkey patched in beremiz customizations
+BACNET_VENDOR_ID = 9999 
+BACNET_VENDOR_NAME = "Beremiz.org"
+BACNET_DEVICE_MODEL_NAME = "Beremiz PLC"
+
+###################################################
+###################################################
+#                                                 #
+#           S L A V E    D E V I C E              # 
+#                                                 #
+###################################################
+###################################################
+
+# NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
+#       The objects are instead instantiated from class FinalCTNClass
+#       FinalCTNClass inherits from: - ConfigTreeNode
+#                                    - The tree node plug (in our case _BacnetSlavePlug)
+#class _BacnetSlavePlug:
+class RootClass:
+    XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+      <xsd:element name="BACnetServerNode">
+        <xsd:complexType>
+          <xsd:attribute name="Network_Interface"      type="xsd:string"  use="optional" default="eth0"/>
+          <xsd:attribute name="UDP_Port_Number"                           use="optional" default="47808">
+            <xsd:simpleType>
+                <xsd:restriction base="xsd:integer">
+                    <xsd:minInclusive value="0"/>
+                    <xsd:maxInclusive value="65535"/>
+                </xsd:restriction>
+            </xsd:simpleType>
+          </xsd:attribute>
+          <xsd:attribute name="BACnet_Communication_Control_Password"     
+                                                       type="xsd:string"  use="optional" default="Malba Tahan"/>
+          <xsd:attribute name="BACnet_Device_ID"                          use="optional" default="0">
+            <xsd:simpleType>
+                <xsd:restriction base="xsd:integer">
+                    <xsd:minInclusive value="0"/>
+                    <xsd:maxInclusive value="4194302"/>
+                </xsd:restriction>
+            </xsd:simpleType>
+          </xsd:attribute>
+          <xsd:attribute name="BACnet_Device_Name"        type="xsd:string"  use="optional" default="Beremiz device 0"/>
+          <xsd:attribute name="BACnet_Device_Location"    type="xsd:string"  use="optional" default=""/>
+          <xsd:attribute name="BACnet_Device_Description" type="xsd:string"  use="optional" default="Beremiz device 0"/>
+          <xsd:attribute name="BACnet_Device_Application_Software_Version" type="xsd:string"  use="optional" default="1.0"/>
+        </xsd:complexType>
+      </xsd:element>
+    </xsd:schema>
+    """
+    # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
+    #       so the Device instance ID is limited from 0 to 22^2-1 = 4194303
+    #       However, 4194303 is reserved for special use (similar to NULL pointer), so last
+    #       valid ID becomes 4194302
+    
+    
+    # The class/object that will render the graphical interface to edit the
+    #    BacnetSlavePlug's configuration parameters. The object of class BacnetSlaveEditorPlug
+    #    will be instantiated by the ConfigTreeNode class.
+    #    This BacnetSlaveEditorPlug object can be accessed from _BacnetSlavePlug as
+    #    'self._View'
+    #    See the following note to understand how this is possible!
+    #
+    # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
+    #       The objects are instead instantiated from class FinalCTNClass
+    #       FinalCTNClass inherits from: - ConfigTreeNode
+    #                                    - The tree node plug (in our case _BacnetSlavePlug)
+    #
+    #       This means that objects of class _BacnetSlavePlug may safely access all the members
+    #       of classes ConfigTreeNode as well as FinalCTNClass (since they are always instantiated
+    #       as a FinalCTNClass)
+    EditorType = BacnetSlaveEditorPlug
+    
+    # The following classes follow the model/viewer design pattern
+    #
+    # _BacnetSlavePlug       - contains the model (i.e. configuration parameters)
+    # BacnetSlaveEditorPlug  - contains the viewer (and editor, so it includes the 'controller' part of the 
+    #                                    design pattern which in this case is not separated from the viewer)
+    #
+    # The _BacnetSlavePlug      object is 'permanent', i.e. it exists as long as the beremiz project is open 
+    # The BacnetSlaveEditorPlug object is 'transient', i.e. it exists only while the editor is visible/open
+    #                                                         in the editing panel. It is destoryed whenever
+    #                                                         the user closes the corresponding tab in the 
+    #                                                         editing panel, and a new object is created when
+    #                                                         the editor is re-opened.
+    #
+    # _BacnetSlavePlug contains:  AV_ObjTable, ...
+    #                             (these are the objects that actually store the config parameters or 'model'
+    #                              and are therefore stored to a file)
+    #
+    # _BacnetSlavePlug contains:  AV_VarEditor, ...
+    #                             (these are the objects that implement a grid table to edit/view the 
+    #                              corresponding mode parameters)
+    #  
+    #  Logic:
+    #    - The xx_VarEditor classes inherit from wx.grid.Grid
+    #    - The xx_ObjTable  classes inherit from wx.grid.PyGridTableBase
+    #  To be more precise, the inheritance tree is actually:
+    #    xx_VarEditor -> ObjectGrid -> CustomGrid   -> wx.grid.Grid
+    #    xx_ObjTable  -> ObjectTable -> CustomTable -> wx.grid.PyGridTableBase)
+    #
+    #  Note that wx.grid.Grid is prepared to work with wx.grid.PyGridTableBase as the container of
+    #  data that is displayed and edited in the Grid.
+
+    
+    ConfNodeMethods = [
+        {"bitmap"  : "ExportSlave",
+         "name"    : _("Export slave"), 
+         "tooltip" : _("Export BACnet slave to EDE file"),
+         "method"  : "_ExportBacnetSlave"},
+    ]
+    
+    def __init__(self):
+        # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables 
+        #   in this BACnet server.
+        self.ObjTablesData = {}
+        self.ObjTablesData[ "AV_Obj"] = [] # Each list will contain an entry for each row in the xxxxVar grid!!
+        self.ObjTablesData[ "AO_Obj"] = [] #   Each entry/row will be a dictionary
+        self.ObjTablesData[ "AI_Obj"] = [] #     Each dictionary will contain all entries/data 
+        self.ObjTablesData[ "BV_Obj"] = [] #     for one row in the grid.
+        self.ObjTablesData[ "BO_Obj"] = [] # Same structure as explained above...
+        self.ObjTablesData[ "BI_Obj"] = [] # Same structure as explained above...
+        self.ObjTablesData["MSV_Obj"] = [] # Same structure as explained above...
+        self.ObjTablesData["MSO_Obj"] = [] # Same structure as explained above...
+        self.ObjTablesData["MSI_Obj"] = [] # Same structure as explained above...
+        
+        self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version":1} 
+                                                # EDE files inlcude extra parameters (ex. file version)
+                                                # We would like to save the parameters the user configures
+                                                # so they are available the next time the user opens the project.
+                                                # Since this plugin is only storing the ObjTablesData[] dict
+                                                # to file, we add that info to this dictionary too.
+                                                # Yes, I know this is kind of a hack.
+        
+        filepath = self.GetFileName()
+        if(os.path.isfile(filepath)):
+            self.LoadFromFile(filepath)
+
+        self.ObjTables = {}
+        self.ObjTables[ "AV_Obj"] = ObjectTable(self, self.ObjTablesData[ "AV_Obj"],  AVObject)
+        self.ObjTables[ "AO_Obj"] = ObjectTable(self, self.ObjTablesData[ "AO_Obj"],  AOObject)
+        self.ObjTables[ "AI_Obj"] = ObjectTable(self, self.ObjTablesData[ "AI_Obj"],  AIObject)
+        self.ObjTables[ "BV_Obj"] = ObjectTable(self, self.ObjTablesData[ "BV_Obj"],  BVObject)
+        self.ObjTables[ "BO_Obj"] = ObjectTable(self, self.ObjTablesData[ "BO_Obj"],  BOObject)
+        self.ObjTables[ "BI_Obj"] = ObjectTable(self, self.ObjTablesData[ "BI_Obj"],  BIObject)
+        self.ObjTables["MSV_Obj"] = ObjectTable(self, self.ObjTablesData["MSV_Obj"], MSVObject)
+        self.ObjTables["MSO_Obj"] = ObjectTable(self, self.ObjTablesData["MSO_Obj"], MSOObject)
+        self.ObjTables["MSI_Obj"] = ObjectTable(self, self.ObjTablesData["MSI_Obj"], MSIObject)
+        #   list containing the data in the table <--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
+
+
+    ######################################
+    # Functions to be called by CTNClass #
+    ######################################
+    # The following functions would be somewhat equvalent to virtual functions/methods in C++ classes
+    #  They will be called by the base class (CTNClass) from which this _BacnetSlavePlug class derives.
+    
+    def GetCurrentNodeName(self):
+        return self.CTNName()
+
+    def GetFileName(self):
+        return os.path.join(self.CTNPath(), 'bacnet_slave')
+    
+    def OnCTNSave(self, from_project_path=None):
+       return self.SaveToFile(self.GetFileName())
+
+
+    def CTNTestModified(self):
+        # self.ChangesToSave: Check whether any of the parameters, defined in the XSD above, were changed.
+        #                     This is handled by the ConfigTreeNode class
+        #                     (Remember that no objects are ever instantiated from _BacnetSlavePlug. 
+        #                      Objects are instead created from FinalCTNClass, which derives from
+        #                      _BacnetSlavePlug and ConfigTreeNode. This means that we can exceptionally
+        #                      consider that all objects of type _BacnetSlavePlug will also be a ConfigTreeNode).
+        result = self.ChangesToSave or self.ObjTables[ "AV_Obj"].ChangesToSave \
+                                    or self.ObjTables[ "AO_Obj"].ChangesToSave \
+                                    or self.ObjTables[ "AI_Obj"].ChangesToSave \
+                                    or self.ObjTables[ "BV_Obj"].ChangesToSave \
+                                    or self.ObjTables[ "BO_Obj"].ChangesToSave \
+                                    or self.ObjTables[ "BI_Obj"].ChangesToSave \
+                                    or self.ObjTables["MSV_Obj"].ChangesToSave \
+                                    or self.ObjTables["MSO_Obj"].ChangesToSave \
+                                    or self.ObjTables["MSI_Obj"].ChangesToSave
+        return result
+
+    ### Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
+    ##def _OpenView(self, name=None, onlyopened=False):
+        ##print "_BacnetSlavePlug._OpenView() Called!!!"
+        ##ConfigTreeNode._OpenView(self, name, onlyopened)
+        ###print self._View
+        #####if self._View is not None:
+            #####self._View.SetBusId(self.GetCurrentLocation())
+        ##return self._View
+
+
+    def GetVariableLocationTree(self):
+        current_location = self.GetCurrentLocation()
+        # see comment in CTNGenerate_C regarding identical line of code!
+        locstr = ".".join(map(str,current_location))
+        
+        # IDs used by BACnet to identify object types/class.
+        #     OBJECT_ANALOG_INPUT       =  0,
+        #     OBJECT_ANALOG_OUTPUT      =  1,
+        #     OBJECT_ANALOG_VALUE       =  2,
+        #     OBJECT_BINARY_INPUT       =  3,
+        #     OBJECT_BINARY_OUTPUT      =  4,
+        #     OBJECT_BINARY_VALUE       =  5,
+        #     OBJECT_MULTI_STATE_INPUT  = 13,
+        #     OBJECT_MULTI_STATE_OUTPUT = 14,
+        #     OBJECT_MULTI_STATE_VALUE  = 19,
+        #
+        #  Since Binary Value, Analog Value, etc. objects may use the same
+        # object ID (since they have distinct class IDs), we must also distinguish them in some way in
+        # the %MX0.3.4 IEC 61131-3 syntax.
+        #
+        # For this reason we add the BACnet class identifier to the %MX0.5.3 location.
+        # For example, for a BACnet plugin in location '0' of the Beremiz configuration tree,
+        #  all      Binary Values will be mapped onto: %MX0.5.xxx    (xxx is object ID)
+        #  all Multi State Values will be mapped onto: %MB0.19.xxx   (xxx is object ID)
+        #  all      Analog Values will be mapped onto: %MD0.2.xxx    (xxx is object ID)
+        #  etc..
+        #
+        #   Value objects will be mapped onto %M
+        #   Input objects will be mapped onto %I
+        #  Output objects will be mapped onto %Q
+                
+        BACnetEntries = []
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "AV_Obj"], 32, 'REAL', 'D', locstr+ '.2', 'Analog Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "AO_Obj"], 32, 'REAL', 'D', locstr+ '.1', 'Analog Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "AI_Obj"], 32, 'REAL', 'D', locstr+ '.0', 'Analog Inputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "BV_Obj"],  1, 'BOOL', 'X', locstr+ '.5', 'Binary Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "BO_Obj"],  1, 'BOOL', 'X', locstr+ '.4', 'Binary Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData[ "BI_Obj"],  1, 'BOOL', 'X', locstr+ '.3', 'Binary Inputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData["MSV_Obj"],  8, 'BYTE', 'B', locstr+'.19', 'Multi State Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData["MSO_Obj"],  8, 'BYTE', 'B', locstr+'.14', 'Multi State Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+                      self.ObjTablesData["MSI_Obj"],  8, 'BYTE', 'B', locstr+'.13', 'Multi State Inputs'))
+
+        return  {"name": self.BaseParams.getName(),
+                 "type": LOCATION_CONFNODE,
+                 "location": locstr + ".x",
+                 "children": BACnetEntries}
+
+
+    ############################
+    # Helper functions/methods #
+    ############################
+    # a helper function to GetVariableLocationTree()
+    def GetSlaveLocationTree(self, ObjTablesData, size_in_bits, IECdatatype, location_size, location_str, name):
+        BACnetObjectEntries = []
+        for  xx_ObjProp in ObjTablesData:
+            BACnetObjectEntries.append({
+                "name": str(xx_ObjProp["Object Identifier"]) + ': ' + xx_ObjProp["Object Name"],
+                "type": LOCATION_VAR_MEMORY, # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
+                "size": size_in_bits, # 1 or 16
+                "IEC_type": IECdatatype, # 'BOOL', 'WORD', ...
+                "var_name": "var_name", # seems to be ignored??
+                "location": location_size + location_str + "." + str(xx_ObjProp["Object Identifier"]),
+                "description": "description", # seems to be ignored?
+                "children": []})
+        
+        BACnetEntries = []        
+        return  {"name": name,
+                 "type": LOCATION_CONFNODE,
+                 "location": location_str + ".x",
+                 "children": BACnetObjectEntries}
+    
+    
+    # Returns a dictionary with:
+    #      keys: names  of BACnet objects
+    #     value: number of BACnet objects using this same name 
+    #            (values larger than 1 indicates an error as BACnet requires unique names)
+    def GetObjectNamesCount(self):
+        # The dictionary is built by first creating a list containing the names of _ALL_ 
+        # BACnet objects currently configured by the user (using the GUI)
+        ObjectNames = []
+        ObjectNames.extend(self.ObjTables[ "AV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables[ "AO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables[ "AI_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables[ "BV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables[ "BO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables[ "BI_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
+        # This list is then transformed into a collections.Counter class
+        # Which is then transformed into a dictionary using dict()
+        return dict(Counter(ObjectNames))
+      
+    # Check whether the current configuration contains BACnet objects configured
+    # with the same identical object name  (returns True or False)
+    def HasDuplicateObjectNames(self):
+        ObjectNamesCount = self.GetObjectNamesCount()
+        for ObjName in ObjectNamesCount:
+            if ObjectNamesCount[ObjName] > 1:
+                return True
+        return False
+
+    # Check whether any object ID is used more than once (not valid in BACnet)
+    # (returns True or False)
+    def HasDuplicateObjectIDs(self):
+        res =        self.ObjTables[ "AV_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables[ "AO_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables[ "AI_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables[ "BV_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables[ "BO_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables[ "BI_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["MSV_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["MSO_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["MSI_Obj"].HasDuplicateObjectIDs()
+        return res
+
+    
+    #######################################################
+    # Methods related to files (saving/loading/exporting) #
+    #######################################################
+    def SaveToFile(self, filepath):
+        # Save node data in file
+        # The configuration data declared in the XSD string will be saved by the ConfigTreeNode class,
+        # so we only need to save the data that is stored in ObjTablesData objects
+        # Note that we do not store the ObjTables objects. ObjTables is of a class that 
+        # contains more stuff we do not need to store. Actually it is a bad idea to store
+        # this extra stuff (as we would make the files we generate dependent on the actual
+        # version of the wx library we are using!!! Remember that ObjTables evetually
+        # derives/inherits from wx.grid.PyGridTableBase). Another reason not to store the whole
+        # object is because it is not pickable (i.e. pickle.dump() cannot handle it)!!
+        try:
+            fd = open(filepath,   "w")
+            pickle.dump(self.ObjTablesData, fd)
+            fd.close()
+            # On successfull save, reset flags to indicate no more changes that need saving
+            self.ObjTables[ "AV_Obj"].ChangesToSave = False
+            self.ObjTables[ "AO_Obj"].ChangesToSave = False
+            self.ObjTables[ "AI_Obj"].ChangesToSave = False
+            self.ObjTables[ "BV_Obj"].ChangesToSave = False
+            self.ObjTables[ "BO_Obj"].ChangesToSave = False
+            self.ObjTables[ "BI_Obj"].ChangesToSave = False
+            self.ObjTables["MSV_Obj"].ChangesToSave = False
+            self.ObjTables["MSO_Obj"].ChangesToSave = False
+            self.ObjTables["MSI_Obj"].ChangesToSave = False
+            return True
+        except:
+            return _("Unable to save to file \"%s\"!")%filepath
+
+    def LoadFromFile(self, filepath):
+        # Load the data that is saved in SaveToFile()
+        try:
+            fd = open(filepath,   "r")
+            self.ObjTablesData = pickle.load(fd)
+            fd.close()
+            return True
+        except:
+            return _("Unable to load file \"%s\"!")%filepath
+
+    def _ExportBacnetSlave(self):
+        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, 
+                               _("Choose a file"), 
+                               os.path.expanduser("~"), 
+                               "%s_EDE.csv" % self.CTNName(),  
+                               _("EDE files (*_EDE.csv)|*_EDE.csv|All files|*.*"),
+                               wx.SAVE|wx.OVERWRITE_PROMPT)
+        if dialog.ShowModal() == wx.ID_OK:
+            result = self.GenerateEDEFile(dialog.GetPath())
+            result = False
+            if result:
+                self.GetCTRoot().logger.write_error(_("Error: Export slave failed\n"))
+        dialog.Destroy()  
+
+
+    def GenerateEDEFile(self, filename):
+        template_file_dir     = os.path.join(os.path.split(__file__)[0],"ede_files")
+        
+        #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+        # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
+        BACnet_Device_ID = self.BACnetServerNode.getBACnet_Device_ID()
+        
+        # The EDE file contains a header that includes general project data (name, author, ...)
+        # Instead of asking the user for this data, we get it from the configuration
+        # of the Beremiz project itself.
+        # We ask the root Config Tree Node for the data...
+        ProjProp = {}
+        FileProp = {}
+        CTN_Root      = self.GetCTRoot()   # this should be an object of class ProjectController
+        Project       = CTN_Root.Project   # this should be an object capable of parsing
+                                           # PLCopen XML files. The parser is created automatically
+                                           # (i.e. using GenerateParserFromXSD() from xmlclass module)
+                                           # using the PLCopen XSD file defining the format of the XML.
+                                           # See the file plcopen/plcopen.py
+        if Project is not None:
+          # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
+          # plcopn/plcopen.py    We cannot rely on their existance
+          if getattr(Project, "getcontentHeader", None) is not None:
+            ProjProp = Project.getcontentHeader()
+            # getcontentHeader() returns a dictionary. Available keys are:
+            # "projectName", "projectVersion", "modificationDateTime", 
+            # "organization", "authorName", "language", "pageSize", "scaling"
+          if getattr(Project, "getfileHeader", None) is not None:
+            FileProp = Project.getfileHeader()
+            # getfileHeader() returns a dictionary. Available keys are:
+            # "companyName", "companyURL", "productName", "productVersion",
+            # "productRelease", "creationDateTime", "contentDescription"
+
+        ProjName   = ""
+        if "projectName" in ProjProp:
+            ProjName    = ProjProp["projectName"]
+        ProjAuthor = ""
+        if "companyName" in FileProp:
+            ProjAuthor += "(" + FileProp["companyName"] + ")"
+        if "authorName" in ProjProp:
+            ProjAuthor  = ProjProp["authorName"] + " " + ProjAuthor
+            
+        projdata_dict = {}
+        projdata_dict["Project Name"]     = ProjName
+        projdata_dict["Project Author"]   = ProjAuthor
+        projdata_dict["Current Time"]     = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        projdata_dict["EDE file version"] = self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"]
+
+        # Next time we generate an EDE file, use another version!
+        self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"] += 1
+
+        AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
+
+        BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
+
+        MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
+        
+        Objects_List = []
+        for  ObjType,     params_format   in [
+            ("AV" ,  AX_params_format ), ("AO" ,  AX_params_format ), ("AI" ,  AX_params_format ),
+            ("BV" ,  BX_params_format ), ("BO" ,  BX_params_format ), ("BI" ,  BX_params_format ),
+            ("MSV", MSX_params_format ), ("MSO", MSX_params_format ), ("MSI", MSX_params_format )
+            ]:
+            self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
+            for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
+                Objects_List.append(params_format % ObjProp)
+        
+        # Normalize filename
+        for extension in ["_EDE.csv", "_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
+            if filename.lower().endswith(extension.lower()):
+                filename = filename[:-len(extension)]
+        
+        # EDE_header
+        generate_file_name    = filename + "_EDE.csv"
+        template_file_name    = os.path.join(template_file_dir,"template_EDE.csv")
+        generate_file_content = open(template_file_name).read() % projdata_dict
+        generate_file_handle  = open(generate_file_name,'w')
+        generate_file_handle  .write(generate_file_content)
+        generate_file_handle  .write("\n".join(Objects_List))
+        generate_file_handle  .close()
+        
+        # templates of remaining files do not need changes. They are simply copied unchanged!
+        for extension in ["_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
+            generate_file_name    = filename + extension
+            template_file_name    = os.path.join(template_file_dir,"template" + extension)
+            generate_file_content = open(template_file_name).read()
+            generate_file_handle  = open(generate_file_name,'w')
+            generate_file_handle  .write(generate_file_content)
+            generate_file_handle  .close()
+
+    
+    #############################
+    # Generate the source files #
+    #############################
+    def CTNGenerate_C(self, buildpath, locations):        
+        # Determine the current location in Beremiz's project configuration tree 
+        current_location = self.GetCurrentLocation()
+        # The current location of this plugin in Beremiz's configuration tree, separated by underscores 
+        #  NOTE: Since BACnet plugin currently does not use sub-branches in the tree (in other words, this
+        #        _BacnetSlavePlug class was actually renamed as the RootClass), the current_location_dots
+        #        will actually be a single number (e.g.: 0 or 3 or 6, corresponding to the location
+        #        in which the plugin was inserted in the Beremiz configuration tree on Beremiz's left panel).
+        locstr = "_".join(map(str,current_location))
+
+        # First check whether all the current parameters (inserted by user in the GUI) are valid...
+        if self.HasDuplicateObjectNames():
+            self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object names.\n")%(locstr, self.CTNName()))
+            raise Exception, False
+            # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
+
+        if self.HasDuplicateObjectIDs():
+            self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object identifiers.\n")%(locstr, self.CTNName()))
+            raise Exception, False
+            # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
+            
+        #-------------------------------------------------------------------------------
+        # Create and populate the loc_dict dictionary with all parameters needed to configure
+        #  the generated source code (.c and .h files)
+        #-------------------------------------------------------------------------------
+        
+        # 1) Create the dictionary (loc_dict = {})
+        loc_dict = {}
+        loc_dict["locstr"]              =         locstr
+
+        #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+        # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
+        loc_dict["network_interface"]            = self.BACnetServerNode.getNetwork_Interface()
+        loc_dict["port_number"]                  = self.BACnetServerNode.getUDP_Port_Number()
+        loc_dict["BACnet_Device_ID"]             = self.BACnetServerNode.getBACnet_Device_ID()
+        loc_dict["BACnet_Device_Name"]           = self.BACnetServerNode.getBACnet_Device_Name()
+        loc_dict["BACnet_Comm_Control_Password"] = self.BACnetServerNode.getBACnet_Communication_Control_Password()
+        loc_dict["BACnet_Device_Location"]       = self.BACnetServerNode.getBACnet_Device_Location()
+        loc_dict["BACnet_Device_Description"]    = self.BACnetServerNode.getBACnet_Device_Description()
+        loc_dict["BACnet_Device_AppSoft_Version"]= self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
+        loc_dict["BACnet_Vendor_ID"] = BACNET_VENDOR_ID
+        loc_dict["BACnet_Vendor_Name"] = BACNET_VENDOR_NAME
+        loc_dict["BACnet_Model_Name"] = BACNET_DEVICE_MODEL_NAME
+
+        # 2) Add the data specific to each BACnet object type
+        # For each BACnet object type, start off by creating some intermediate helpful lists
+        #  a) parameters_list containing the strings that will
+        #     be included in the C source code, and which will initialize the struct with the 
+        #     object (Analog Value, Binary Value, or Multi State Value) parameters
+        #  b) locatedvar_list containing the strings that will
+        #     declare the memory to store the located variables, as well as the 
+        #     pointers (required by matiec) that point to that memory.
+
+        # format for delaring IEC 61131-3 variable (and pointer) onto which BACnet object is mapped
+        locvar_format = '%(Ctype)s ___%(loc)s_%(Object Identifier)s; ' + \
+                        '%(Ctype)s *__%(loc)s_%(Object Identifier)s = &___%(loc)s_%(Object Identifier)s;'
+
+        # format for initializing a ANALOG_VALUE_DESCR struct in C code
+        #    also valid for ANALOG_INPUT and ANALOG_OUTPUT
+        AX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+                          '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
+        # format for initializing a BINARY_VALUE_DESCR struct in C code
+        #    also valid for BINARY_INPUT and BINARY_OUTPUT
+        BX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+                          '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
+
+        # format for initializing a MULTISTATE_VALUE_DESCR struct in C code
+        #    also valid for MULTISTATE_INPUT and MULTISTATE_OUTPUT
+        MSX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+                           '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
+
+        AV_locstr  = 'MD' + locstr+'_2'  # see the comment in GetVariableLocationTree() to grok the '_2'
+        AO_locstr  = 'QD' + locstr+'_1'  # see the comment in GetVariableLocationTree() to grok the '_1'
+        AI_locstr  = 'ID' + locstr+'_0'  # see the comment in GetVariableLocationTree() to grok the '_0'
+        BV_locstr  = 'MX' + locstr+'_5'  # see the comment in GetVariableLocationTree() to grok the '_5'
+        BO_locstr  = 'QX' + locstr+'_4'  # see the comment in GetVariableLocationTree() to grok the '_4'
+        BI_locstr  = 'IX' + locstr+'_3'  # see the comment in GetVariableLocationTree() to grok the '_3'
+        MSV_locstr = 'MB' + locstr+'_19' # see the comment in GetVariableLocationTree() to grok the '_19'
+        MSO_locstr = 'QB' + locstr+'_14' # see the comment in GetVariableLocationTree() to grok the '_14'
+        MSI_locstr = 'IB' + locstr+'_13' # see the comment in GetVariableLocationTree() to grok the '_13'
+        
+        
+        for  ObjType,  ObjLocStr,     params_format   in [
+            ("AV"   ,  AV_locstr,  AX_params_format ),
+            ("AO"   ,  AO_locstr,  AX_params_format ),
+            ("AI"   ,  AI_locstr,  AX_params_format ),
+            ("BV"   ,  BV_locstr,  BX_params_format ),
+            ("BO"   ,  BO_locstr,  BX_params_format ),
+            ("BI"   ,  BI_locstr,  BX_params_format ),
+            ("MSV"  , MSV_locstr, MSX_params_format ),
+            ("MSO"  , MSO_locstr, MSX_params_format ),
+            ("MSI"  , MSI_locstr, MSX_params_format )
+            ]:
+            parameters_list = []
+            locatedvar_list = []
+            self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
+            for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
+                ObjProp["loc" ] = ObjLocStr
+                parameters_list.append(params_format % ObjProp)
+                locatedvar_list.append(locvar_format % ObjProp)
+            loc_dict[ ObjType + "_count"]           =          len( parameters_list )
+            loc_dict[ ObjType + "_param"]           =   ",\n".join( parameters_list )
+            loc_dict[ ObjType + "_lvars"]           =    "\n".join( locatedvar_list )
+
+        #----------------------------------------------------------------------
+        # Create the C source files that implement the BACnet server
+        #----------------------------------------------------------------------
+        
+        # Names of the .c files that will be generated, based on a template file with same name
+        #   (names without '.c'  --> this will be added later)
+        #   main server.c file is handled separately
+        Generated_BACnet_c_mainfile = "server"
+        Generated_BACnet_c_files = ["ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
+
+        # Names of the .h files that will be generated, based on a template file with same name
+        #   (names without '.h'  --> this will be added later)
+        Generated_BACnet_h_files = ["server", "device", "config_bacnet_for_beremiz",
+                                    "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
+                                   ]
+
+        # Generate the files with the source code
+        postfix = "_".join(map(str, current_location))
+        template_file_dir     = os.path.join(os.path.split(__file__)[0],"runtime")
+        
+        def generate_file(file_name, extension): 
+            generate_file_name    = os.path.join(buildpath, "%s_%s.%s"%(file_name,postfix,extension))
+            template_file_name    = os.path.join(template_file_dir,"%s.%s"%(file_name,extension))
+            generate_file_content = open(template_file_name).read() % loc_dict
+            generate_file_handle  = open(generate_file_name,'w')
+            generate_file_handle  .write(generate_file_content)
+            generate_file_handle  .close()
+
+        for file_name in Generated_BACnet_c_files:
+            generate_file(file_name, "c")
+        for file_name in Generated_BACnet_h_files:
+            generate_file(file_name, "h")
+        generate_file(Generated_BACnet_c_mainfile, "c")
+        Generated_BACnet_c_mainfile_name = \
+            os.path.join(buildpath, "%s_%s.%s"%(Generated_BACnet_c_mainfile,postfix,"c"))
+
+        #----------------------------------------------------------------------
+        # Finally, define the compilation and linking commands and flags
+        #----------------------------------------------------------------------
+        
+        LDFLAGS = []
+        # when using dynamically linked library...
+        #LDFLAGS.append(' -lbacnet')   
+        #LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
+        #LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
+        # when using static library:
+        LDFLAGS.append(' "'+os.path.join(BacnetLibraryPath, "libbacnet.a")+'"')  
+
+        CFLAGS   = ' -I"'+BacnetIncludePath+'"'
+        CFLAGS  += ' -I"'+BacnetIncludePortPath+'"'
+        
+        return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
+
+
+
+###################################################
+###################################################
+#                                                 #
+#             R O O T    C L A S S                # 
+#                                                 #
+###################################################
+###################################################
+#class RootClass:
+    #XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+    #<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+      #<xsd:element name="BACnetRoot">
+      #</xsd:element>
+    #</xsd:schema>
+    #"""
+    #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug,  "Bacnet Slave")
+                       ##,("XXX",_XXXXPlug, "XXX")
+                       #]
+    
+    #def CTNGenerate_C(self, buildpath, locations):        
+        #return [], "", True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_EDE.csv	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,8 @@
+#EDE (Engineering-Data-Exchange) - generated by Beremiz BACnet plugin;;;;;;;;;;;;;;;
+PROJECT_NAME;%(Project Name)s;;;;;;;;;;;;;;
+VERSION_OF_REFERENCEFILE;%(EDE file version)d;;;;;;;;;;;;;;
+TIMESTAMP_OF_LAST_CHANGE;%(Current Time)s;;;;;;;;;;;;;;
+AUTHOR_OF_LAST_CHANGE;%(Project Author)s;;;;;;;;;;;;;;
+VERSION_OF_LAYOUT;2.1;;;;;;;;;;;;;;
+#mandatory;mandatory;mandatory;mandatory;mandatory;optional;optional;optional;optional;optional;optional;optional;optional;optional;optional;optional
+#keyname;device obj.-instance;object-name;object-type;object-instance;description;present-value-default;min-present-value;max-present-value;settable;supports COV;hi-limit;low-limit;state-text-reference;unit-code;vendor-specific-address
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_ObjTypes.csv	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,27 @@
+#Encoding of BACnet Object Types;;;
+#Code;Object Type;;
+0;Analog Input;;
+1;Analog Output;;
+2;Analog Value;;
+3;Binary Input ;;
+4;Binary Output;;
+5;Binary Value;;
+6;Calendar;;
+7;Command;;
+8;Device;;
+9;Event-Enrollment;;
+10;File;;
+11;Group;;
+12;Loop;;
+13;Multistate Input;;
+14;Multistate Output
+15;Notification Class
+16;Program
+17;Schedule
+18;Averaging
+19;Multistate Value
+20;Trend Log
+21;Life Safety Point
+22;Life Safety Zone
+23;Accumulator
+24;Pulse Converter
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_StateTexts.csv	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,2 @@
+#State Text Reference;;;;;;;
+#Reference Number;Text 1;Text 2;Text 3;Text 4;Text 5;...;Text n
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_Units.csv	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,264 @@
+#Encoding of BACnet Engineering Units;
+#Code;Unit Text
+166;METERS-PER-SECOND-PER-SECOND
+0;SQUARE-METERS
+116;SQUARE-CENTIMETERS
+1;SQUARE-FEET
+115;SQUARE-INCHES
+105;CURRENCY1
+106;CURRENCY2
+107;CURRENCY3
+108;CURRENCY4
+109;CURRENCY5
+110;CURRENCY6
+111;CURRENCY7
+112;CURRENCY8
+113;CURRENCY9
+114;CURRENCY10
+2;MILLIAMPERES
+3;AMPERES
+167;AMPERES-PER-METER
+168;AMPERES-PER-SQUARE-METER
+169;AMPERE-SQUARE-METERS
+199;DECIBELS
+200;DECIBELS-MILLIVOLT
+201;DECIBELS-VOLT
+170;FARADS
+171;HENRYS
+4;OHMS
+237;OHM-METER-SQUARED-PER-METER
+172;OHM-METERS
+145;MILLIOHMS
+122;KILOHMS
+123;MEGOHMS
+190;MICROSIEMENS
+202;MILLISIEMENS
+173;SIEMENS
+174;SIEMENS-PER-METER
+175;TESLAS
+5;VOLTS
+124;MILLIVOLTS
+6;KILOVOLTS
+7;MEGAVOLTS
+8;VOLT-AMPERES
+9;KILOVOLT-AMPERES
+10;MEGAVOLT-AMPERES
+11;VOLT-AMPERES-REACTIVE
+12;KILOVOLT-AMPERES-REACTIVE
+13;MEGAVOLT-AMPERES-REACTIVE
+176;VOLTS-PER-DEGREE-KELVIN
+177;VOLTS-PER-METER
+14;DEGREES-PHASE
+15;POWER-FACTOR
+178;WEBERS
+238;AMPERE-SECONDS
+239;VOLT-AMPERE-HOURS
+240;KILOVOLT-AMPERE-HOURS
+241;MEGAVOLT-AMPERE-HOURS
+242;VOLT-AMPERE-HOURS-REACTIVE
+243;KILOVOLT-AMPERE-HOURS-REACTIVE
+244;MEGAVOLT-AMPERE-HOURS-REACTIVE
+245;VOLT-SQUARE-HOURS
+246;AMPERE-SQUARE-HOURS
+16;JOULES
+17;KILOJOULES
+125;KILOJOULES-PER-KILOGRAM
+126;MEGAJOULES
+18;WATT-HOURS
+19;KILOWATT-HOURS
+146;MEGAWATT-HOURS
+203;WATT-HOURS-REACTIVE
+204;KILOWATT-HOURS-REACTIVE
+205;MEGAWATT-HOURS-REACTIVE
+20;BTUS
+147;KILO-BTUS
+148;MEGA-BTUS
+21;THERMS
+22;TON-HOURS
+23;JOULES-PER-KILOGRAM-DRY-AIR
+149;KILOJOULES-PER-KILOGRAM-DRY-AIR
+150;MEGAJOULES-PER-KILOGRAM-DRY-AIR
+24;BTUS-PER-POUND-DRY-AIR
+117;BTUS-PER-POUND
+127;JOULES-PER-DEGREE-KELVIN
+151;KILOJOULES-PER-DEGREE-KELVIN
+152;MEGAJOULES-PER-DEGREE-KELVIN
+128;JOULES-PER-KILOGRAM-DEGREE-KELVIN
+153;NEWTON
+25;CYCLES-PER-HOUR
+26;CYCLES-PER-MINUTE
+27;HERTZ
+129;KILOHERTZ
+130;MEGAHERTZ
+131;PER-HOUR
+28;GRAMS-OF-WATER-PER-KILOGRAM-DRY-AIR
+29;PERCENT-RELATIVE-HUMIDITY
+194;MICROMETERS
+30;MILLIMETERS
+118;CENTIMETERS
+193;KILOMETERS
+31;METERS
+32;INCHES
+33;FEET
+179;CANDELAS
+180;CANDELAS-PER-SQUARE-METER
+34;WATTS-PER-SQUARE-FOOT
+35;WATTS-PER-SQUARE-METER
+36;LUMENS
+37;LUXES
+38;FOOT-CANDLES
+196;MILLIGRAMS
+195;GRAMS
+39;KILOGRAMS
+40;POUNDS-MASS
+41;TONS
+154;GRAMS-PER-SECOND
+155;GRAMS-PER-MINUTE
+42;KILOGRAMS-PER-SECOND
+43;KILOGRAMS-PER-MINUTE
+44;KILOGRAMS-PER-HOUR
+119;POUNDS-MASS-PER-SECOND
+45;POUNDS-MASS-PER-MINUTE
+46;POUNDS-MASS-PER-HOUR
+156;TONS-PER-HOUR
+132;MILLIWATTS
+47;WATTS
+48;KILOWATTS
+49;MEGAWATTS
+50;BTUS-PER-HOUR
+157;KILO-BTUS-PER-HOUR
+247;JOULE-PER-HOURS
+51;HORSEPOWER
+52;TONS-REFRIGERATION
+53;PASCALS
+133;HECTOPASCALS
+54;KILOPASCALS
+134;MILLIBARS
+55;BARS
+56;POUNDS-FORCE-PER-SQUARE-INCH
+206;MILLIMETERS-OF-WATER
+57;CENTIMETERS-OF-WATER
+58;INCHES-OF-WATER
+59;MILLIMETERS-OF-MERCURY
+60;CENTIMETERS-OF-MERCURY
+61;INCHES-OF-MERCURY
+62;DEGREES-CELSIUS
+63;DEGREES-KELVIN
+181;DEGREES-KELVIN-PER-HOUR
+182;DEGREES-KELVIN-PER-MINUTE
+64;DEGREES-FAHRENHEIT
+65;DEGREE-DAYS-CELSIUS
+66;DEGREE-DAYS-FAHRENHEIT
+120;DELTA-DEGREES-FAHRENHEIT
+121;DELTA-DEGREES-KELVIN
+67;YEARS
+68;MONTHS
+69;WEEKS
+70;DAYS
+71;HOURS
+72;MINUTES
+73;SECONDS
+158;HUNDREDTHS-SECONDS
+159;MILLISECONDS
+160;NEWTON-METERS
+161;MILLIMETERS-PER-SECOND
+162;MILLIMETERS-PER-MINUTE
+74;METERS-PER-SECOND
+163;METERS-PER-MINUTE
+164;METERS-PER-HOUR
+75;KILOMETERS-PER-HOUR
+76;FEET-PER-SECOND
+77;FEET-PER-MINUTE
+78;MILES-PER-HOUR
+79;CUBIC-FEET
+80;CUBIC-METERS
+81;IMPERIAL-GALLONS
+197;MILLILITERS
+82;LITERS
+83;US-GALLONS
+142;CUBIC-FEET-PER-SECOND
+84;CUBIC-FEET-PER-MINUTE
+254;MILLION-STANDARD-CUBIC-FEET-PER-MINUTE
+191;CUBIC-FEET-PER-HOUR
+248;CUBIC-FEET-PER-DAY
+47808;STANDARD-CUBIC-FEET-PER-DAY
+47809;MILLION-STANDARD-CUBIC-FEET-PER-DAY
+47810;THOUSAND-CUBIC-FEET-PER-DAY
+47811;THOUSAND-STANDARD-CUBIC-FEET-PER-DAY
+47812;POUNDS-MASS-PER-DAY
+85;CUBIC-METERS-PER-SECOND
+165;CUBIC-METERS-PER-MINUTE
+135;CUBIC-METERS-PER-HOUR
+249;CUBIC-METERS-PER-DAY
+86;IMPERIAL-GALLONS-PER-MINUTE
+198;MILLILITERS-PER-SECOND
+87;LITERS-PER-SECOND
+88;LITERS-PER-MINUTE
+136;LITERS-PER-HOUR
+89;US-GALLONS-PER-MINUTE
+192;US-GALLONS-PER-HOUR
+90;DEGREES-ANGULAR
+91;DEGREES-CELSIUS-PER-HOUR
+92;DEGREES-CELSIUS-PER-MINUTE
+93;DEGREES-FAHRENHEIT-PER-HOUR
+94;DEGREES-FAHRENHEIT-PER-MINUTE
+183;JOULE-SECONDS
+186;KILOGRAMS-PER-CUBIC-METER
+137;KILOWATT-HOURS-PER-SQUARE-METER
+138;KILOWATT-HOURS-PER-SQUARE-FOOT
+250;WATT-HOURS-PER-CUBIC-METER
+251;JOULES-PER-CUBIC-METER
+139;MEGAJOULES-PER-SQUARE-METER
+140;MEGAJOULES-PER-SQUARE-FOOT
+252;MOLE-PERCENT
+95;NO-UNITS
+187;NEWTON-SECONDS
+188;NEWTONS-PER-METER
+96;PARTS-PER-MILLION
+97;PARTS-PER-BILLION
+253;PASCAL-SECONDS
+98;PERCENT
+143;PERCENT-OBSCURATION-PER-FOOT
+144;PERCENT-OBSCURATION-PER-METER
+99;PERCENT-PER-SECOND
+100;PER-MINUTE
+101;PER-SECOND
+102;PSI-PER-DEGREE-FAHRENHEIT
+103;RADIANS
+184;RADIANS-PER-SECOND
+104;REVOLUTIONS-PER-MINUTE
+185;SQUARE-METERS-PER-NEWTON
+189;WATTS-PER-METER-PER-DEGREE-KELVIN
+141;WATTS-PER-SQUARE-METER-DEGREE-KELVIN
+207;PER-MILLE
+208;GRAMS-PER-GRAM
+209;KILOGRAMS-PER-KILOGRAM
+210;GRAMS-PER-KILOGRAM
+211;MILLIGRAMS-PER-GRAM
+212;MILLIGRAMS-PER-KILOGRAM
+213;GRAMS-PER-MILLILITER
+214;GRAMS-PER-LITER
+215;MILLIGRAMS-PER-LITER
+216;MICROGRAMS-PER-LITER
+217;GRAMS-PER-CUBIC-METER
+218;MILLIGRAMS-PER-CUBIC-METER
+219;MICROGRAMS-PER-CUBIC-METER
+220;NANOGRAMS-PER-CUBIC-METER
+221;GRAMS-PER-CUBIC-CENTIMETER
+222;BECQUERELS
+223;KILOBECQUERELS
+224;MEGABECQUERELS
+225;GRAY
+226;MILLIGRAY
+227;MICROGRAY
+228;SIEVERTS
+229;MILLISIEVERTS
+230;MICROSIEVERTS
+231;MICROSIEVERTS-PER-HOUR
+47814;MILLIREMS
+47815;MILLIREMS-PER-HOUR
+232;DECIBELS-A
+233;NEPHELOMETRIC-TURBIDITY-UNIT
+234;PH
+235;GRAMS-PER-SQUARE-METER
+236;MINUTES-PER-DEGREE-KELVIN
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ai.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,497 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Input Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>     /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "ai_%(locstr)s.h"
+
+
+
+/* initial value for present_value property of each object */ 
+#define AI_VALUE_INIT (0)
+
+/* The IEC 61131-3 located variables mapped onto the Analog Input objects of BACnet protocol */
+%(AI_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Input Objects */
+#define MAX_ANALOG_INPUTS %(AI_count)s
+static ANALOG_INPUT_DESCR AI_Descr[MAX_ANALOG_INPUTS] = {
+%(AI_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Input_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,   /* R  R ( 75) */
+    PROP_OBJECT_NAME,         /* R  R ( 77) */
+    PROP_OBJECT_TYPE,         /* R  R ( 79) */
+    PROP_PRESENT_VALUE,       /* W  R ( 85) */
+    PROP_STATUS_FLAGS,        /* R  R (111) */
+    PROP_EVENT_STATE,         /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,      /* W  R ( 81) */
+    PROP_UNITS,               /* W  R (117) */
+//  PROP_PROPERTY_LIST,       /* R  R (371) */
+    -1
+};
+
+static const int Analog_Input_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_DESCRIPTION,         /* R  O ( 28) */
+    -1
+};
+
+static const int Analog_Input_Properties_Proprietary[] = {
+    -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Analog_Inputs_Init() based off the values
+ * stored in Analog_Input_Properties_Required 
+ *           Analog_Input_Properties_Optional
+ *           Analog_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Input_Properties_List[64];
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Input_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Analog_Input_Properties_Required;
+    if (pOptional)
+        *pOptional = Analog_Input_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Analog_Input_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Input_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Analog_Input_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+                                           Analog_Input_Properties_Required);
+        len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+                                           Analog_Input_Properties_Optional);
+        len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+                                           Analog_Input_Properties_Proprietary);
+
+        for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+            // MJS: the following line in th original demo code was commented out so we do not
+            //      overwrite the initial values configured by the user in beremiz IDE
+            // memset(&AI_Descr[i], 0x00, sizeof(ANALOG_INPUT_DESCR));
+            AI_Descr[i].Present_Value  = AI_VALUE_INIT;
+            AI_Descr[i].Out_Of_Service = 0;
+            AI_Descr[i].Event_State    = 0;
+//          AI_Descr[i].Units = UNITS_NO_UNITS;
+        }
+    }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Input_Valid_Instance(
+    uint32_t object_instance)
+{
+    return (Analog_Input_Instance_To_Index(object_instance) < MAX_ANALOG_INPUTS);
+}
+
+/* the number of Analog Input Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Analog_Input_Count(void) {return MAX_ANALOG_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Analog_Input_Index_To_Instance(unsigned index) {return AI_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Input_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_ANALOG_INPUTS; index++)
+        if (object_instance == AI_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_ANALOG_INPUTS;
+}
+
+
+
+
+float Analog_Input_Present_Value(
+    uint32_t object_instance)
+{
+    float value = AI_VALUE_INIT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Analog_Input_Instance_To_Index(object_instance);
+    if (index < MAX_ANALOG_INPUTS)
+        value = AI_Descr[index].Present_Value;
+
+    return value;
+}
+
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Input_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Input_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_INPUTS)
+        status = characterstring_init_ansi(object_name, AI_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Analog_Input_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Input_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_INPUTS)
+        status = characterstring_init_ansi(object_name, AI_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Analog_Input_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    float real_value = (float) 1.414;
+    unsigned object_index = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    apdu = rpdata->application_data;
+
+    object_index = Analog_Input_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_ANALOG_INPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_ANALOG_INPUT,
+                rpdata->object_instance);
+            break;
+
+        case PROP_OBJECT_NAME:
+            Analog_Input_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_DESCRIPTION:
+            Analog_Input_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_ANALOG_INPUT);
+            break;
+
+        case PROP_PRESENT_VALUE:
+            real_value = Analog_Input_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_real(&apdu[0], real_value);
+            break;
+
+        case PROP_STATUS_FLAGS:
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                AI_Descr[object_index].Out_Of_Service);
+
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+
+        case PROP_EVENT_STATE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+
+        case PROP_OUT_OF_SERVICE:
+            state = AI_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+
+        case PROP_UNITS:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], AI_Descr[object_index].Units);
+            break;
+
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Analog_Input_Properties_List,
+//                              property_list_count(Analog_Input_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) &&
+        (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Input_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+    ANALOG_INPUT_DESCR *CurrentAI;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    object_index = Analog_Input_Instance_To_Index(wp_data->object_instance);
+    if (object_index < MAX_ANALOG_INPUTS)
+        CurrentAI = &AI_Descr[object_index];
+    else
+        return false;
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL,
+                &wp_data->error_class, &wp_data->error_code);
+            if (!status) {
+                wp_data->error_class = ERROR_CLASS_PROPERTY;
+                wp_data->error_code  = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                status = false; // not really necessary here.
+            } else {
+                if (!AI_Descr[object_index].Out_Of_Service) {
+                    /* input objects can only be written to when Out_Of_Service is true! */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code  = ERROR_CODE_WRITE_ACCESS_DENIED;
+                    status = false;
+                } else {
+                    AI_Descr[object_index].Present_Value = value.type.Real;
+                    status = true;
+                }
+            }
+            break;
+      
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = CurrentAI->Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAI->Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !CurrentAI->Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    CurrentAI->Present_Value = *(CurrentAI->Located_Var_ptr);
+            }
+            break;
+        }
+
+        case PROP_UNITS:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAI->Units = value.type.Enumerated;
+            }
+            break;
+
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_DESCRIPTION:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+void  Analog_Input_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AI_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(AI_Descr[i].Located_Var_ptr) = Analog_Input_Present_Value(AI_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Analog_Input_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AI_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        AI_Descr[i].Present_Value = *(AI_Descr[i].Located_Var_ptr);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ai.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,102 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AI_H
+#define AI_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct Analog_Input_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        float   *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint16_t Units;
+      
+        /* stores the current value */
+        /* one entry per priority value */
+        float Present_Value;
+        unsigned Event_State:3;
+        bool Out_Of_Service;
+    } ANALOG_INPUT_DESCR;
+
+
+    void Analog_Input_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Analog_Input_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Analog_Input_Count(
+        void);
+    uint32_t Analog_Input_Index_To_Instance(
+        unsigned index);
+    unsigned Analog_Input_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Analog_Input_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    int Analog_Input_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Analog_Input_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    float Analog_Input_Present_Value(
+        uint32_t object_instance);
+
+    char *Analog_Input_Description(
+        uint32_t instance);
+
+    uint16_t Analog_Input_Units(
+        uint32_t instance);
+
+    bool Analog_Input_Out_Of_Service(
+        uint32_t instance);
+    void Analog_Input_Out_Of_Service_Set(
+        uint32_t instance,
+        bool oos_flag);
+
+    void Analog_Input_Init(
+        void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ao.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,620 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Output Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>     /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "ao_%(locstr)s.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since the values are floats, we use NAN (Not A Number) as our NULL value. */
+/* WARNING: Never use comparisons like 'if (value == AO_LEVEL_NULL)'
+ *           as it will always return false, even if both values are NAN.
+ *          Use instead the negated version 'if (value != AO_LEVEL_NULL)'
+ *           and add an 'else' to the 'if' condition if necessary.
+ *         However, some compilers may screw this up if they do not
+ *         implement IEEE 754 properly. It is probably best to stick with
+ *         the isnan() macro if available.
+ */
+#define AO_VALUE_NULL NAN
+#define AO_VALUE_IS_NULL(x)  (isnan(x))
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define AO_VALUE_RELINQUISH_DEFAULT (0.0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Analog Output objects of BACnet protocol */
+%(AO_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Output Objects */
+#define MAX_ANALOG_OUTPUTS %(AO_count)s
+static ANALOG_OUTPUT_DESCR AO_Descr[MAX_ANALOG_OUTPUTS] = {
+%(AO_param)s
+};
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Output_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,       /* R  R ( 75) */
+    PROP_OBJECT_NAME,             /* R  R ( 77) */
+    PROP_OBJECT_TYPE,             /* R  R ( 79) */
+    PROP_PRESENT_VALUE,           /* W  W ( 85) */
+    PROP_STATUS_FLAGS,            /* R  R (111) */
+    PROP_EVENT_STATE,             /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,          /* W  R ( 81) */
+    PROP_UNITS,                   /* W  R (117) */
+    PROP_PRIORITY_ARRAY,          /* R  R ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  R (104) */    
+//  PROP_PROPERTY_LIST,           /* R  R (371) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  R (431) */   
+    -1
+};
+
+static const int Analog_Output_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_DESCRIPTION,             /* R  O ( 28) */
+    -1
+};
+
+static const int Analog_Output_Properties_Proprietary[] = {
+    -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Analog_Outputs_Init() based off the values
+ * stored in Analog_Output_Properties_Required 
+ *           Analog_Output_Properties_Optional
+ *           Analog_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Output_Properties_List[64];
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Output_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Analog_Output_Properties_Required;
+    if (pOptional)
+        *pOptional = Analog_Output_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Analog_Output_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Output_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Analog_Output_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+                                           Analog_Output_Properties_Required);
+        len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+                                           Analog_Output_Properties_Optional);
+        len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+                                           Analog_Output_Properties_Proprietary);
+
+        for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+            // MJS: the following line in th original demo code was commented out so we do not
+            //      overwrite the initial values configured by the user in beremiz IDE
+            // memset(&AO_Descr[i], 0x00, sizeof(ANALOG_OUTPUT_DESCR));
+            for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+                AO_Descr[i].Present_Value[j]  = AO_VALUE_NULL;
+            }
+            AO_Descr[i].Out_Of_Service = 0;
+            AO_Descr[i].Event_State    = 0;
+//          AO_Descr[i].Units = UNITS_NO_UNITS;
+        }
+    }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Output_Valid_Instance(
+    uint32_t object_instance)
+{
+    return (Analog_Output_Instance_To_Index(object_instance) < MAX_ANALOG_OUTPUTS);
+}
+
+/* the number of Analog Output Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Analog_Output_Count(void) {return MAX_ANALOG_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Analog_Output_Index_To_Instance(unsigned index) {return AO_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Output_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_ANALOG_OUTPUTS; index++)
+        if (object_instance == AO_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_ANALOG_OUTPUTS;
+}
+
+
+
+
+float Analog_Output_Present_Value(
+    uint32_t object_instance)
+{
+    float value = AO_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Analog_Output_Instance_To_Index(object_instance);
+    if (index < MAX_ANALOG_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!AO_VALUE_IS_NULL(AO_Descr[index].Present_Value[i])) {
+                value = AO_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Analog_Output_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Analog_Output_Instance_To_Index(object_instance);
+    if (index < MAX_ANALOG_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!AO_VALUE_IS_NULL(AO_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+/* For a given object instance-number, sets the present-value at a given
+ * priority 1..16 (except 6, see ASHRAE 135-2016, section 19.2.2)
+ */
+bool Analog_Output_Present_Value_Set(
+    uint32_t object_instance,
+    float value,
+    uint8_t priority)
+{
+    unsigned index = 0;
+
+    index = Analog_Output_Instance_To_Index(object_instance);
+    if (index >= MAX_ANALOG_OUTPUTS) 
+      return false;
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    priority--;
+    AO_Descr[index].Present_Value[priority] = value;
+    return true;
+}
+
+
+
+bool Analog_Output_Present_Value_Relinquish(
+    uint32_t object_instance,
+    unsigned priority)
+{
+    unsigned index = 0;
+
+    index = Analog_Output_Instance_To_Index(object_instance);
+    if (index >= MAX_ANALOG_OUTPUTS)
+      return false;
+
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+ 
+    priority--;
+    AO_Descr[index].Present_Value[priority] = AO_VALUE_NULL;
+    return true;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Output_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Output_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_OUTPUTS)
+        status = characterstring_init_ansi(object_name, AO_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Analog_Output_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Output_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_OUTPUTS)
+        status = characterstring_init_ansi(object_name, AO_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Analog_Output_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    float real_value = (float) 1.414;
+    unsigned object_index = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    apdu = rpdata->application_data;
+
+    object_index = Analog_Output_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_ANALOG_OUTPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_ANALOG_OUTPUT,
+                rpdata->object_instance);
+            break;
+
+        case PROP_OBJECT_NAME:
+            Analog_Output_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_DESCRIPTION:
+            Analog_Output_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_ANALOG_OUTPUT);
+            break;
+
+        case PROP_PRESENT_VALUE:
+            real_value = Analog_Output_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_real(&apdu[0], real_value);
+            break;
+
+        case PROP_STATUS_FLAGS:
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                AO_Descr[object_index].Out_Of_Service);
+
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+
+        case PROP_EVENT_STATE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+
+        case PROP_OUT_OF_SERVICE:
+            state = AO_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(AO_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                AO_VALUE_IS_NULL,
+                                encode_application_real)
+            break;
+
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Analog_Output_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+
+        case PROP_RELINQUISH_DEFAULT:
+            real_value = AO_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_real(&apdu[0], real_value);
+            break;
+
+        case PROP_UNITS:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], AO_Descr[object_index].Units);
+            break;
+
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Analog_Output_Properties_List,
+//                              property_list_count(Analog_Output_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+        (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Output_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+    ANALOG_OUTPUT_DESCR *CurrentAO;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    object_index = Analog_Output_Instance_To_Index(wp_data->object_instance);
+    if (object_index < MAX_ANALOG_OUTPUTS)
+        CurrentAO = &AO_Descr[object_index];
+    else
+        return false;
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            if (value.tag == BACNET_APPLICATION_TAG_REAL) {
+                if (Analog_Output_Present_Value_Set(wp_data->object_instance,
+                        value.type.Real, wp_data->priority)) {
+                    status = true;
+                } else if (wp_data->priority == 6) {
+                    /* Command priority 6 is reserved for use by Minimum On/Off
+                       algorithm and may not be used for other purposes in any
+                       object. */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                } else {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    status =
+                        Analog_Output_Present_Value_Relinquish
+                        (wp_data->object_instance, wp_data->priority);
+                }
+                if (!status) {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            }
+            break;
+
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = CurrentAO->Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAO->Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !CurrentAO->Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    CurrentAO->Present_Value[BACNET_MAX_PRIORITY-1] =
+                                             *(CurrentAO->Located_Var_ptr);
+            }
+            break;
+        }
+
+        case PROP_UNITS:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAO->Units = value.type.Enumerated;
+            }
+            break;
+
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_DESCRIPTION:
+        case PROP_RELINQUISH_DEFAULT:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_PROPERTY_LIST:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+void  Analog_Output_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AO_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(AO_Descr[i].Located_Var_ptr) = Analog_Output_Present_Value(AO_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Analog_Output_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AO_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        AO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(AO_Descr[i].Located_Var_ptr);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ao.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,106 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AO_H
+#define AO_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct Analog_Output_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        float   *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint16_t Units;
+      
+        /* stores the current value */
+        /* one entry per priority value */
+        float Present_Value[BACNET_MAX_PRIORITY];
+        unsigned Event_State:3;
+        bool Out_Of_Service;
+    } ANALOG_OUTPUT_DESCR;
+
+
+    void Analog_Output_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Analog_Output_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Analog_Output_Count(
+        void);
+    uint32_t Analog_Output_Index_To_Instance(
+        unsigned index);
+    unsigned Analog_Output_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Analog_Output_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    int Analog_Output_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Analog_Output_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    bool Analog_Output_Present_Value_Set(
+        uint32_t object_instance,
+        float value,
+        uint8_t priority);
+    float Analog_Output_Present_Value(
+        uint32_t object_instance);
+
+    char *Analog_Output_Description(
+        uint32_t instance);
+
+    uint16_t Analog_Output_Units(
+        uint32_t instance);
+
+    bool Analog_Output_Out_Of_Service(
+        uint32_t instance);
+    void Analog_Output_Out_Of_Service_Set(
+        uint32_t instance,
+        bool oos_flag);
+
+    void Analog_Output_Init(
+        void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/av.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,622 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Value Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>     /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "av_%(locstr)s.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since the values are floats, we use NAN (Not A Number) as our NULL value. */
+/* WARNING: Never use comparisons like 'if (value == AO_LEVEL_NULL)'
+ *           as it will always return false, even if both values are NAN.
+ *          Use instead the negated version 'if (value != AO_LEVEL_NULL)'
+ *           and add an 'else' to the 'if' condition if necessary.
+ *         However, some compilers may screw this up if they do not
+ *         implement IEEE 754 properly. It is probably best to stick with
+ *         the isnan() macro if available.
+ */
+#define AV_VALUE_NULL NAN
+#define AV_VALUE_IS_NULL(x)  (isnan(x))
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define AV_VALUE_RELINQUISH_DEFAULT (0.0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Analog Value objects of BACnet protocol */
+%(AV_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Value Objects */
+#define MAX_ANALOG_VALUES %(AV_count)s
+static ANALOG_VALUE_DESCR AV_Descr[MAX_ANALOG_VALUES] = {
+%(AV_param)s
+};
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Value_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,   /* R  R ( 75) */
+    PROP_OBJECT_NAME,         /* R  R ( 77) */
+    PROP_OBJECT_TYPE,         /* R  R ( 79) */
+    PROP_PRESENT_VALUE,       /* W  R ( 85) */
+    PROP_STATUS_FLAGS,        /* R  R (111) */
+    PROP_EVENT_STATE,         /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,      /* W  R ( 81) */
+    PROP_UNITS,               /* W  R (117) */
+//  PROP_PROPERTY_LIST,       /* R  R (371) */
+    -1
+};
+
+static const int Analog_Value_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_DESCRIPTION,             /* R  O ( 28) */
+    /* required if Present_Value is writable (which is true in our case!) */
+    PROP_PRIORITY_ARRAY,          /* R  O ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  O (104) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  O (431) */   
+    -1
+};
+
+static const int Analog_Value_Properties_Proprietary[] = {
+    -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Analog_Values_Init() based off the values
+ * stored in Analog_Value_Properties_Required 
+ *           Analog_Value_Properties_Optional
+ *           Analog_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Value_Properties_List[64];
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Value_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Analog_Value_Properties_Required;
+    if (pOptional)
+        *pOptional = Analog_Value_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Analog_Value_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Analog_Value_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Analog_Value_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+                                           Analog_Value_Properties_Required);
+        len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+                                           Analog_Value_Properties_Optional);
+        len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+                                           Analog_Value_Properties_Proprietary);
+
+        for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+            // MJS: the following line in th original demo code was commented out so we do not
+            //      overwrite the initial values configured by the user in beremiz IDE
+            // memset(&AV_Descr[i], 0x00, sizeof(ANALOG_VALUE_DESCR));
+            for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+                AV_Descr[i].Present_Value[j]  = AV_VALUE_NULL;
+            }
+            AV_Descr[i].Out_Of_Service = 0;
+            AV_Descr[i].Event_State    = 0;
+//          AV_Descr[i].Units = UNITS_NO_UNITS;
+        }
+    }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Value_Valid_Instance(
+    uint32_t object_instance)
+{
+    return (Analog_Value_Instance_To_Index(object_instance) < MAX_ANALOG_VALUES);
+}
+
+/* the number of Analog Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Analog_Value_Count(void) {return MAX_ANALOG_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Analog_Value_Index_To_Instance(unsigned index) {return AV_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Value_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_ANALOG_VALUES; index++)
+        if (object_instance == AV_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_ANALOG_VALUES;
+}
+
+
+
+
+float Analog_Value_Present_Value(
+    uint32_t object_instance)
+{
+    float value = AV_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Analog_Value_Instance_To_Index(object_instance);
+    if (index < MAX_ANALOG_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!AV_VALUE_IS_NULL(AV_Descr[index].Present_Value[i])) {
+                value = AV_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Analog_Value_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Analog_Value_Instance_To_Index(object_instance);
+    if (index < MAX_ANALOG_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!AV_VALUE_IS_NULL(AV_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+/* For a given object instance-number, sets the present-value at a given
+ * priority 1..16 (except 6, see ASHRAE 135-2016, section 19.2.2)
+ */
+bool Analog_Value_Present_Value_Set(
+    uint32_t object_instance,
+    float value,
+    uint8_t priority)
+{
+    unsigned index = 0;
+
+    index = Analog_Value_Instance_To_Index(object_instance);
+    if (index >= MAX_ANALOG_VALUES) 
+      return false;
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    priority--;
+    AV_Descr[index].Present_Value[priority] = value;
+    return true;
+}
+
+
+
+bool Analog_Value_Present_Value_Relinquish(
+    uint32_t object_instance,
+    unsigned priority)
+{
+    unsigned index = 0;
+
+    index = Analog_Value_Instance_To_Index(object_instance);
+    if (index >= MAX_ANALOG_VALUES)
+      return false;
+
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+ 
+    priority--;
+    AV_Descr[index].Present_Value[priority] = AV_VALUE_NULL;
+    return true;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Value_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Value_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_VALUES)
+        status = characterstring_init_ansi(object_name, AV_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Analog_Value_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Analog_Value_Instance_To_Index(object_instance);
+
+    if (index < MAX_ANALOG_VALUES)
+        status = characterstring_init_ansi(object_name, AV_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Analog_Value_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    float real_value = (float) 1.414;
+    unsigned object_index = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+    ANALOG_VALUE_DESCR *CurrentAV;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    apdu = rpdata->application_data;
+
+    object_index = Analog_Value_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_ANALOG_VALUES) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_ANALOG_VALUE,
+                rpdata->object_instance);
+            break;
+
+        case PROP_OBJECT_NAME:
+            Analog_Value_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_DESCRIPTION:
+            Analog_Value_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_ANALOG_VALUE);
+            break;
+
+        case PROP_PRESENT_VALUE:
+            real_value = Analog_Value_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_real(&apdu[0], real_value);
+            break;
+
+        case PROP_STATUS_FLAGS:
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                AV_Descr[object_index].Out_Of_Service);
+
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+
+        case PROP_EVENT_STATE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+
+        case PROP_OUT_OF_SERVICE:
+            state = AV_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(AV_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                AV_VALUE_IS_NULL,
+                                encode_application_real)
+            break;
+
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Analog_Value_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+
+        case PROP_RELINQUISH_DEFAULT:
+            real_value = AV_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_real(&apdu[0], real_value);
+            break;
+
+        case PROP_UNITS:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], AV_Descr[object_index].Units);
+            break;
+
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Analog_Value_Properties_List,
+//                              property_list_count(Analog_Value_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+        (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Analog_Value_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+    ANALOG_VALUE_DESCR *CurrentAV;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    object_index = Analog_Value_Instance_To_Index(wp_data->object_instance);
+    if (object_index < MAX_ANALOG_VALUES)
+        CurrentAV = &AV_Descr[object_index];
+    else
+        return false;
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            if (value.tag == BACNET_APPLICATION_TAG_REAL) {
+                if (Analog_Value_Present_Value_Set(wp_data->object_instance,
+                        value.type.Real, wp_data->priority)) {
+                    status = true;
+                } else if (wp_data->priority == 6) {
+                    /* Command priority 6 is reserved for use by Minimum On/Off
+                       algorithm and may not be used for other purposes in any
+                       object. */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                } else {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    status =
+                        Analog_Value_Present_Value_Relinquish
+                        (wp_data->object_instance, wp_data->priority);
+                }
+                if (!status) {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            }
+            break;
+
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = CurrentAV->Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAV->Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !CurrentAV->Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    CurrentAV->Present_Value[BACNET_MAX_PRIORITY-1] =
+                                             *(CurrentAV->Located_Var_ptr);
+            }
+            break;
+        }
+
+        case PROP_UNITS:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                CurrentAV->Units = value.type.Enumerated;
+            }
+            break;
+
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_DESCRIPTION:
+        case PROP_RELINQUISH_DEFAULT:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_PROPERTY_LIST:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+void  Analog_Value_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(AV_Descr[i].Located_Var_ptr) = Analog_Value_Present_Value(AV_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Analog_Value_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (AV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        AV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(AV_Descr[i].Located_Var_ptr);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/av.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,106 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AV_H
+#define AV_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct analog_value_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        float   *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint16_t Units;
+      
+        /* stores the current value */
+        /* one entry per priority value */
+        float Present_Value[BACNET_MAX_PRIORITY];
+        unsigned Event_State:3;
+        bool Out_Of_Service;
+    } ANALOG_VALUE_DESCR;
+
+
+    void Analog_Value_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Analog_Value_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Analog_Value_Count(
+        void);
+    uint32_t Analog_Value_Index_To_Instance(
+        unsigned index);
+    unsigned Analog_Value_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Analog_Value_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    int Analog_Value_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Analog_Value_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    bool Analog_Value_Present_Value_Set(
+        uint32_t object_instance,
+        float value,
+        uint8_t priority);
+    float Analog_Value_Present_Value(
+        uint32_t object_instance);
+
+    char *Analog_Value_Description(
+        uint32_t instance);
+
+    uint16_t Analog_Value_Units(
+        uint32_t instance);
+
+    bool Analog_Value_Out_Of_Service(
+        uint32_t instance);
+    void Analog_Value_Out_Of_Service_Set(
+        uint32_t instance,
+        bool oos_flag);
+
+    void Analog_Value_Init(
+        void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bi.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,492 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Input Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bi_%(locstr)s.h"
+#include "handlers.h"
+
+
+
+/* initial value for present_value property of each object */ 
+#define BI_VALUE_INIT (0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Binary Input objects of BACnet protocol */
+%(BI_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Input Objects */
+#define MAX_BINARY_INPUTS %(BI_count)s
+static BINARY_INPUT_DESCR BI_Descr[MAX_BINARY_INPUTS] = {
+%(BI_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Input_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,   /* R  R ( 75) */
+    PROP_OBJECT_NAME,         /* R  R ( 77) */
+    PROP_OBJECT_TYPE,         /* R  R ( 79) */
+    PROP_PRESENT_VALUE,       /* W  R ( 85) */
+    PROP_STATUS_FLAGS,        /* R  R (111) */
+    PROP_EVENT_STATE,         /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,      /* W  R ( 81) */
+    PROP_POLARITY,            /* R  R ( 84) */
+//  PROP_PROPERTY_LIST,       /* R  R (371) */
+    -1
+};
+
+static const int Binary_Input_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_DESCRIPTION,         /* R  O ( 28) */
+    -1
+};
+
+static const int Binary_Input_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Binary_Input_Init() based off the values
+ * stored in Binary_Input_Properties_Required 
+ *           Binary_Input_Properties_Optional
+ *           Binary_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Input_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Input_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Binary_Input_Properties_Required;
+    if (pOptional)
+        *pOptional = Binary_Input_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Binary_Input_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Input_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Input_Init() called!\n");
+    
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Binary_Input_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+                                           Binary_Input_Properties_Required);
+        len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+                                           Binary_Input_Properties_Optional);
+        len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+                                           Binary_Input_Properties_Proprietary);
+
+        /* initialize all the binary values priority arrays to NULL */
+        for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+            BI_Descr[i].Present_Value = BI_VALUE_INIT;
+            BI_Descr[i].Polarity      = POLARITY_NORMAL;
+        }
+    }
+
+    return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Input_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Binary_Input_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Binary_Input_Instance_To_Index(object_instance) < MAX_BINARY_INPUTS);
+}
+
+
+/* the number of Binary Input Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Binary_Input_Count(void)   {return MAX_BINARY_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Binary_Input_Index_To_Instance(unsigned index) {return BI_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Input_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_BINARY_INPUTS; index++)
+        if (object_instance == BI_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_BINARY_INPUTS;
+}
+
+
+
+BACNET_BINARY_PV Binary_Input_Present_Value(
+    uint32_t object_instance)
+{
+    BACNET_BINARY_PV value = BI_VALUE_INIT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Input_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+    index = Binary_Input_Instance_To_Index(object_instance);
+    if (index < MAX_BINARY_INPUTS)
+        value = BI_Descr[index].Present_Value;
+
+    return value;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Input_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Input_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_INPUTS)
+        status = characterstring_init_ansi(object_name, BI_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Binary_Input_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Input_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_INPUTS)
+        status = characterstring_init_ansi(object_name, BI_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Binary_Input_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Input_Read_Property() called!\n");
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+    
+    object_index = Binary_Input_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_BINARY_INPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+    
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_BINARY_INPUT,
+                rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Binary_Input_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            Binary_Input_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_BINARY_INPUT);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Binary_Input_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_enumerated(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            state = BI_Descr[object_index].Out_Of_Service;
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = BI_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_POLARITY:
+            apdu_len = encode_application_enumerated(&apdu[0],
+                                BI_Descr[object_index].Polarity);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Binary_Input_Properties_List,
+//                              property_list_count(Binary_Input_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code  = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Input_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    unsigned int priority = 0;
+    BACNET_BINARY_PV level = BINARY_NULL;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    /*  only array properties can have array options */
+    if (wp_data->array_index != BACNET_ARRAY_ALL) {
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Binary_Input_Write_Property() is called
+     */
+    object_index = Binary_Input_Instance_To_Index(wp_data->object_instance);
+    
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+                &wp_data->error_class, &wp_data->error_code);
+            if (!status) {
+                wp_data->error_class = ERROR_CLASS_PROPERTY;
+                wp_data->error_code  = ERROR_CODE_VALUE_OUT_OF_RANGE;
+            } else {
+                if (!BI_Descr[object_index].Out_Of_Service) {
+                    /* input objects can only be written to when Out_Of_Service is true! */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code  = ERROR_CODE_WRITE_ACCESS_DENIED;
+                    status = false; // not really necessary here.
+                } else {
+                    if (!(value.type.Enumerated <= MAX_BINARY_PV)) {
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code  = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                        status = false;
+                    } else {
+                        level = (BACNET_BINARY_PV) value.type.Enumerated;
+                        BI_Descr[object_index].Present_Value = level;
+                        status = true;
+                    }
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = BI_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                BI_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !BI_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    BI_Descr[object_index].Present_Value =
+                                           *(BI_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_DESCRIPTION:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_POLARITY:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Binary_Input_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BI_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(BI_Descr[i].Located_Var_ptr) = Binary_Input_Present_Value(BI_Descr[i].Object_Identifier);
+    }
+}
+  
+void  Binary_Input_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BI_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value (0 is false, all other values are true)
+        if (*(BI_Descr[i].Located_Var_ptr))
+            BI_Descr[i].Present_Value = BINARY_ACTIVE;
+        else
+            BI_Descr[i].Present_Value = BINARY_INACTIVE;
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bi.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,104 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BI_H
+#define BI_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct Binary_Input_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        /* stores the current value */
+        BACNET_BINARY_PV Present_Value;
+        /* Writable out-of-service allows others to play with our Present Value */
+        /* without changing the physical output */
+        bool             Out_Of_Service;
+        BACNET_POLARITY  Polarity;
+    } BINARY_INPUT_DESCR;
+
+
+    void Binary_Input_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Binary_Input_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Binary_Input_Count(
+        void);
+    uint32_t Binary_Input_Index_To_Instance(
+        unsigned index);
+    unsigned Binary_Input_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Binary_Input_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    char *Binary_Input_Description(
+        uint32_t instance);
+
+    BACNET_BINARY_PV Binary_Input_Present_Value(
+        uint32_t instance);
+    bool Binary_Input_Present_Value_Set(
+        uint32_t instance,
+        BACNET_BINARY_PV value);
+
+    bool Binary_Input_Out_Of_Service(
+        uint32_t instance);
+    void Binary_Input_Out_Of_Service_Set(
+        uint32_t instance,
+        bool value);
+
+    char *Binary_Input_Description(
+        uint32_t instance);
+
+    int Binary_Input_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Binary_Input_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    void Binary_Input_Init(
+        void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bo.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,567 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Output Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bo_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+// BINARY_NULL
+/* test whether value is NULL */
+#define BINARY_OUTPUT_IS_NULL(x) ((x) == BINARY_NULL)
+
+/* When all the priorities are level null, the present value returns
+ * the Relinquish Default value 
+ */
+#define BO_VALUE_RELINQUISH_DEFAULT BINARY_INACTIVE
+
+/* The IEC 61131-3 located variables mapped onto the Binary Output objects of BACnet protocol */
+%(BO_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Output Objects */
+#define MAX_BINARY_OUTPUTS %(BO_count)s
+static BINARY_OUTPUT_DESCR BO_Descr[MAX_BINARY_OUTPUTS] = {
+%(BO_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Output_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,       /* R  R ( 75) */
+    PROP_OBJECT_NAME,             /* R  R ( 77) */
+    PROP_OBJECT_TYPE,             /* R  R ( 79) */
+    PROP_PRESENT_VALUE,           /* W  W ( 85) */
+    PROP_STATUS_FLAGS,            /* R  R (111) */
+    PROP_EVENT_STATE,             /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,          /* W  R ( 81) */
+    PROP_POLARITY,                /* R  R ( 84) */
+    PROP_PRIORITY_ARRAY,          /* R  R ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  R (104) */
+//  PROP_PROPERTY_LIST,           /* R  R (371) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  R (431) */   
+    -1
+};
+
+static const int Binary_Output_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_DESCRIPTION,         /* R  O ( 28) */
+    -1
+};
+
+static const int Binary_Output_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Binary_Output_Init() based off the values
+ * stored in Binary_Output_Properties_Required 
+ *           Binary_Output_Properties_Optional
+ *           Binary_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Output_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Output_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Binary_Output_Properties_Required;
+    if (pOptional)
+        *pOptional = Binary_Output_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Binary_Output_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Output_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Output_Init() called!\n");
+    
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Binary_Output_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+                                           Binary_Output_Properties_Required);
+        len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+                                           Binary_Output_Properties_Optional);
+        len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+                                           Binary_Output_Properties_Proprietary);
+
+        /* initialize all the binary values priority arrays to NULL */
+        for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+            for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+                BO_Descr[i].Present_Value[j] = BINARY_NULL;
+            }
+        }
+    }
+
+    return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Output_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Binary_Output_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Binary_Output_Instance_To_Index(object_instance) < MAX_BINARY_OUTPUTS);
+}
+
+
+/* the number of Binary Output Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Binary_Output_Count(void)   {return MAX_BINARY_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Binary_Output_Index_To_Instance(unsigned index) {return BO_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Output_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_BINARY_OUTPUTS; index++)
+        if (object_instance == BO_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_BINARY_OUTPUTS;
+}
+
+
+
+BACNET_BINARY_PV Binary_Output_Present_Value(
+    uint32_t object_instance)
+{
+    BACNET_BINARY_PV value = BO_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Output_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+    index = Binary_Output_Instance_To_Index(object_instance);
+    if (index < MAX_BINARY_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!BINARY_OUTPUT_IS_NULL(BO_Descr[index].Present_Value[i])) {
+                value = BO_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Binary_Output_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Binary_Output_Instance_To_Index(object_instance);
+    if (index < MAX_BINARY_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!BINARY_OUTPUT_IS_NULL(BO_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Output_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Output_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_OUTPUTS)
+        status = characterstring_init_ansi(object_name, BO_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Binary_Output_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Output_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_OUTPUTS)
+        status = characterstring_init_ansi(object_name, BO_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Binary_Output_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Output_Read_Property() called!\n");
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    object_index = Binary_Output_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_BINARY_OUTPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+    
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_BINARY_OUTPUT,
+                rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Binary_Output_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            Binary_Output_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Binary_Output_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_enumerated(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            state = BO_Descr[object_index].Out_Of_Service;
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = BO_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(BO_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                BINARY_OUTPUT_IS_NULL,
+                                encode_application_enumerated)
+            break;
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Binary_Output_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+
+        case PROP_RELINQUISH_DEFAULT:
+            present_value = BO_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_enumerated(&apdu[0], present_value);
+            break;
+        case PROP_POLARITY:
+            apdu_len = encode_application_enumerated(&apdu[0],
+                                BO_Descr[object_index].Polarity);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Binary_Output_Properties_List,
+//                              property_list_count(Binary_Output_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Output_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    unsigned int priority = 0;
+    BACNET_BINARY_PV level = BINARY_NULL;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    /*  only array properties can have array options */
+    if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Binary_Output_Write_Property() is called
+     */
+    object_index = Binary_Output_Instance_To_Index(wp_data->object_instance);
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
+                priority = wp_data->priority;
+                if (priority && (priority <= BACNET_MAX_PRIORITY) &&
+                    (priority != 6 /* reserved */ ) &&
+                    (value.type.Enumerated <= MAX_BINARY_PV)) {
+                    level = (BACNET_BINARY_PV) value.type.Enumerated;
+                    priority--;
+                    BO_Descr[object_index].Present_Value[priority] = level;
+                    status = true;
+                } else if (priority == 6) {
+                    /* Command priority 6 is reserved for use by Minimum On/Off
+                       algorithm and may not be used for other purposes in any
+                       object. */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                } else {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    level = BINARY_NULL;
+                    priority = wp_data->priority;
+                    if (priority && (priority <= BACNET_MAX_PRIORITY)) {
+                        priority--;
+                        BO_Descr[object_index].Present_Value[priority] = level;
+                    } else {
+                        status = false;
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                    }
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = BO_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                BO_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !BO_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    BO_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+                                           *(BO_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_DESCRIPTION:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+        case PROP_RELINQUISH_DEFAULT:
+        case PROP_POLARITY:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Binary_Output_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BO_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(BO_Descr[i].Located_Var_ptr) = Binary_Output_Present_Value(BO_Descr[i].Object_Identifier);
+    }
+}
+  
+void  Binary_Output_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BO_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(BO_Descr[i].Located_Var_ptr);
+
+        // If the Present_Value was set to an invalid value (i.e. > 1, and < BINARY_NULL)
+        //   then we set it to BINARY_ACTIVE 
+        //   (i.e. we assume 0 is FALSE, all other non NULL values are TRUE)
+        if ((BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_INACTIVE) &&
+            (BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_ACTIVE  ) &&
+            (BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_NULL    ))
+             BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1]  = BINARY_ACTIVE;          
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bo.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,105 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BO_H
+#define BO_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct Binary_Output_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        /* stores the current value */
+        /* one entry per priority value */
+        BACNET_BINARY_PV Present_Value[BACNET_MAX_PRIORITY];
+        /* Writable out-of-service allows others to play with our Present Value */
+        /* without changing the physical output */
+        bool             Out_Of_Service;
+        BACNET_POLARITY  Polarity;
+    } BINARY_OUTPUT_DESCR;
+
+
+    void Binary_Output_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Binary_Output_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Binary_Output_Count(
+        void);
+    uint32_t Binary_Output_Index_To_Instance(
+        unsigned index);
+    unsigned Binary_Output_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Binary_Output_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    char *Binary_Output_Description(
+        uint32_t instance);
+
+    BACNET_BINARY_PV Binary_Output_Present_Value(
+        uint32_t instance);
+    bool Binary_Output_Present_Value_Set(
+        uint32_t instance,
+        BACNET_BINARY_PV value);
+
+    bool Binary_Output_Out_Of_Service(
+        uint32_t instance);
+    void Binary_Output_Out_Of_Service_Set(
+        uint32_t instance,
+        bool value);
+
+    char *Binary_Output_Description(
+        uint32_t instance);
+
+    int Binary_Output_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Binary_Output_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    void Binary_Output_Init(
+        void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bv.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,562 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Value Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bv_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+// BINARY_NULL
+/* test whether value is NULL */
+#define BINARY_VALUE_IS_NULL(x) ((x) == BINARY_NULL)
+
+/* When all the priorities are level null, the present value returns
+ * the Relinquish Default value 
+ */
+#define BV_VALUE_RELINQUISH_DEFAULT BINARY_INACTIVE
+
+/* The IEC 61131-3 located variables mapped onto the Binary Value objects of BACnet protocol */
+%(BV_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Value Objects */
+#define MAX_BINARY_VALUES %(BV_count)s
+static BINARY_VALUE_DESCR BV_Descr[MAX_BINARY_VALUES] = {
+%(BV_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Value_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,   /* R  R ( 75) */
+    PROP_OBJECT_NAME,         /* R  R ( 77) */
+    PROP_OBJECT_TYPE,         /* R  R ( 79) */
+    PROP_PRESENT_VALUE,       /* W  R ( 85) */
+    PROP_STATUS_FLAGS,        /* R  R (111) */
+    PROP_EVENT_STATE,         /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,      /* W  R ( 81) */
+//  PROP_PROPERTY_LIST,       /* R  R (371) */
+    -1
+};
+
+static const int Binary_Value_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_DESCRIPTION,             /* R  O ( 28) */
+    /* required if Present_Value is writable (which is true in our case!) */
+    PROP_PRIORITY_ARRAY,          /* R  O ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  O (104) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  O (431) */   
+    -1
+};
+
+static const int Binary_Value_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Binary_Value_Init() based off the values
+ * stored in Binary_Value_Properties_Required 
+ *           Binary_Value_Properties_Optional
+ *           Binary_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Value_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Value_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Binary_Value_Properties_Required;
+    if (pOptional)
+        *pOptional = Binary_Value_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Binary_Value_Properties_Proprietary;
+
+    return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Binary_Value_Init(
+    void)
+{
+    unsigned i, j;
+    static bool initialized = false;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Value_Init() called!\n");
+    
+    if (!initialized) {
+        initialized = true;
+
+        /* initialize the Binary_Value_Properties_List array */
+        int len = 0;
+        len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+                                           Binary_Value_Properties_Required);
+        len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+                                           Binary_Value_Properties_Optional);
+        len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+                                           Binary_Value_Properties_Proprietary);
+
+        /* initialize all the binary values priority arrays to NULL */
+        for (i = 0; i < MAX_BINARY_VALUES; i++) {
+            for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+                BV_Descr[i].Present_Value[j] = BINARY_NULL;
+            }
+        }
+    }
+
+    return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Value_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Binary_Value_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Binary_Value_Instance_To_Index(object_instance) < MAX_BINARY_VALUES);
+}
+
+
+/* the number of Binary Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Binary_Value_Count(void)   {return MAX_BINARY_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Binary_Value_Index_To_Instance(unsigned index) {return BV_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Value_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_BINARY_VALUES; index++)
+        if (object_instance == BV_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_BINARY_VALUES;
+}
+
+
+
+BACNET_BINARY_PV Binary_Value_Present_Value(
+    uint32_t object_instance)
+{
+    BACNET_BINARY_PV value = BV_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0;
+    unsigned i = 0;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Value_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+    index = Binary_Value_Instance_To_Index(object_instance);
+    if (index < MAX_BINARY_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!BINARY_VALUE_IS_NULL(BV_Descr[index].Present_Value[i])) {
+                value = BV_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Binary_Value_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Binary_Value_Instance_To_Index(object_instance);
+    if (index < MAX_BINARY_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!BINARY_VALUE_IS_NULL(BV_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Value_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Value_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_VALUES)
+        status = characterstring_init_ansi(object_name, BV_Descr[index].Object_Name);
+    
+    return status;
+}
+
+
+
+bool Binary_Value_Object_Description(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool    status = false;
+    unsigned index = Binary_Value_Instance_To_Index(object_instance);
+
+    if (index < MAX_BINARY_VALUES)
+        status = characterstring_init_ansi(object_name, BV_Descr[index].Description);
+    
+    return status;    
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Binary_Value_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    // fprintf(stderr, "BACnet plugin: Binary_Value_Read_Property() called!\n");
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    object_index = Binary_Value_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_BINARY_VALUES) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+    
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_BINARY_VALUE,
+                rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Binary_Value_Object_Name(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            Binary_Value_Object_Description(rpdata->object_instance, &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0], OBJECT_BINARY_VALUE);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Binary_Value_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_enumerated(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            state = BV_Descr[object_index].Out_Of_Service;
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = BV_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(BV_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                BINARY_VALUE_IS_NULL,
+                                encode_application_enumerated)
+            break;
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Binary_Value_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+
+        case PROP_RELINQUISH_DEFAULT:
+            present_value = BV_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_enumerated(&apdu[0], present_value);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Binary_Value_Properties_List,
+//                              property_list_count(Binary_Value_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Binary_Value_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    unsigned int object_index = 0;
+    unsigned int priority = 0;
+    BACNET_BINARY_PV level = BINARY_NULL;
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    /*  only array properties can have array options */
+    if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Binary_Value_Write_Property() is called
+     */
+    object_index = Binary_Value_Instance_To_Index(wp_data->object_instance);
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
+                priority = wp_data->priority;
+                if (priority && (priority <= BACNET_MAX_PRIORITY) &&
+                    (priority != 6 /* reserved */ ) &&
+                    (value.type.Enumerated <= MAX_BINARY_PV)) {
+                    level = (BACNET_BINARY_PV) value.type.Enumerated;
+                    priority--;
+                    BV_Descr[object_index].Present_Value[priority] = level;
+                    status = true;
+                } else if (priority == 6) {
+                    /* Command priority 6 is reserved for use by Minimum On/Off
+                       algorithm and may not be used for other purposes in any
+                       object. */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                } else {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    level = BINARY_NULL;
+                    priority = wp_data->priority;
+                    if (priority && (priority <= BACNET_MAX_PRIORITY)) {
+                        priority--;
+                        BV_Descr[object_index].Present_Value[priority] = level;
+                    } else {
+                        status = false;
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                    }
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = BV_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                BV_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !BV_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    BV_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+                                           *(BV_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_DESCRIPTION:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+        case PROP_RELINQUISH_DEFAULT:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Binary_Value_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(BV_Descr[i].Located_Var_ptr) = Binary_Value_Present_Value(BV_Descr[i].Object_Identifier);
+    }
+}
+  
+void  Binary_Value_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_BINARY_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (BV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(BV_Descr[i].Located_Var_ptr);
+
+        // If the Present_Value was set to an invalid value (i.e. > 1, and < BINARY_NULL)
+        //   then we set it to BINARY_ACTIVE 
+        //   (i.e. we assume 0 is FALSE, all other non NULL values are TRUE)
+        if ((BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_INACTIVE) &&
+            (BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_ACTIVE  ) &&
+            (BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_NULL    ))
+             BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1]  = BINARY_ACTIVE;          
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bv.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,104 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BV_H
+#define BV_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    typedef struct binary_value_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        /* stores the current value */
+        /* one entry per priority value */
+        BACNET_BINARY_PV Present_Value[BACNET_MAX_PRIORITY];
+        /* Writable out-of-service allows others to play with our Present Value */
+        /* without changing the physical output */
+        bool Out_Of_Service;
+    } BINARY_VALUE_DESCR;
+
+
+    void Binary_Value_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    bool Binary_Value_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Binary_Value_Count(
+        void);
+    uint32_t Binary_Value_Index_To_Instance(
+        unsigned index);
+    unsigned Binary_Value_Instance_To_Index(
+        uint32_t object_instance);
+
+    bool Binary_Value_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    char *Binary_Value_Description(
+        uint32_t instance);
+
+    BACNET_BINARY_PV Binary_Value_Present_Value(
+        uint32_t instance);
+    bool Binary_Value_Present_Value_Set(
+        uint32_t instance,
+        BACNET_BINARY_PV value);
+
+    bool Binary_Value_Out_Of_Service(
+        uint32_t instance);
+    void Binary_Value_Out_Of_Service_Set(
+        uint32_t instance,
+        bool value);
+
+    char *Binary_Value_Description(
+        uint32_t instance);
+
+    int Binary_Value_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Binary_Value_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    void Binary_Value_Init(
+        void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/config_bacnet_for_beremiz.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,284 @@
+/**************************************************************************
+*
+* Copyright (C) 2004 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+
+#ifndef CONFIG_BACNET_FOR_BEREMIZ_H
+#define CONFIG_BACNET_FOR_BEREMIZ_H
+
+#ifdef  CONFIG_H
+#error  "config.h already processed! (config_bacnet_for_beremiz.h should be included before config.h)"
+#endif
+
+/* Compilaton options for BACnet library, configured for the BACnet sserver
+ * running on Beremiz.
+ */
+
+/* declare a single physical layer using your compiler define.
+ * see datalink.h for possible defines. 
+ */
+/* Use BACnet/IP */
+#define BACDL_BIP
+
+/* optional configuration for BACnet/IP datalink layers */
+/* other BIP defines (define as 1 to enable):
+    USE_INADDR - uses INADDR_BROADCAST for broadcast and binds using INADDR_ANY
+    USE_CLASSADDR = uses IN_CLASSx_HOST where x=A,B,C or D for broadcast
+*/
+#define BBMD_ENABLED 1
+
+/* name of file in which BDT table will be stored */
+#define BBMD_BACKUP_FILE beremiz_BACnet_BDT_table
+
+/* Enable the Gateway (Routing) functionality here, if desired. */
+#define MAX_NUM_DEVICES 1       /* Just the one normal BACnet Device Object */
+
+
+/* Define your processor architecture as
+   Big Endian (PowerPC,68K,Sparc) or Little Endian (Intel,AVR)
+   ARM and MIPS can be either - what is your setup? */
+
+/* WARNING: The following files are being included:
+ *              <stdib.h>  -->  <endian.h>  -->  <bits/endian.h>
+ * 
+ *          endian.h defines the following constants as:
+ *            #define __LITTLE_ENDIAN  and LITTLE_ENDIAN  as 1234
+ *            #define    __BIG_ENDIAN  and    BIG_ENDIAN  as 4321
+ *            #define    __PDP_ENDIAN  and    PDP_ENDIAN  as 3412
+ * 
+ *          bits/endian.h defines the constant BYTE_ORDER as:
+ *              #define __BYTE_ORDER as __LITTLE_ENDIAN
+ * 
+ *          endian.h then sets the following constants
+ *          (if __USE_BSD is set, which seems to be true):
+ *            # define LITTLE_ENDIAN    __LITTLE_ENDIAN
+ *            # define BIG_ENDIAN       __BIG_ENDIAN
+ *            # define PDP_ENDIAN       __PDP_ENDIAN
+ *            # define BYTE_ORDER       __BYTE_ORDER
+ * 
+ *         CONCLUSION:
+ *           The bacnet library uses the BIG_ENDIAN constant (set to 0, or anything <>0)
+ *           to indicate whether we are compiling on a little or big endian platform.
+ *           However, <stdlib.h> is defining this same constant as '4321' !!!
+ *           The decision to use BIG_ENDIAN as the constant is a unfortunate 
+ *           on the part of the bacnet coders, but we live with it for now...
+ *           We simply start off by undefining the BIG_ENDIAN constant, and carry
+ *           on from there! 
+ */
+#undef BIG_ENDIAN
+
+#ifndef BIG_ENDIAN
+#if defined(__GNUC__) 
+  /* We have GCC, which should define __LITTLE_ENDIAN__ or __BIG_ENDIAN__ */ 
+#  if   defined(__LITTLE_ENDIAN__)
+/*#    warning "Using gcc to determine platform endianness."*/
+#    define BIG_ENDIAN 0
+#  elif defined(__BIG_ENDIAN__)
+/*#    warning "Using gcc to determine platform endianness."*/
+#    define BIG_ENDIAN 1
+#  endif
+#endif /* __GNUC__   */ 
+#endif /* BIG_ENDIAN */
+
+
+/* If we still don't know byte order, try to get it from <endian.h> */
+#ifndef BIG_ENDIAN
+#include <endian.h>
+#  ifdef BYTE_ORDER
+#    if BYTE_ORDER == LITTLE_ENDIAN
+/*#      warning "Using <endian.h> to determine platform endianness."*/
+#      undef  BIG_ENDIAN 
+#      define BIG_ENDIAN 0
+#    elif BYTE_ORDER == BIG_ENDIAN
+/*#      warning "Using <endian.h> to determine platform endianness."*/
+#      undef  BIG_ENDIAN 
+#      define BIG_ENDIAN 1
+#    else
+#      undef  BIG_ENDIAN
+#    endif
+#  endif /* BYTE_ORDER */
+#endif   /* BIG_ENDIAN */
+
+
+#ifndef BIG_ENDIAN
+#error   "Unable to determine platform's byte order. Aborting compilation."
+#elif   BIG_ENDIAN
+/*#warning "Compiling for BIG endian platform."*/
+#else
+/*#warning "Compiling for LITTLE endian platform."*/
+#endif
+
+
+
+/* Define your Vendor Identifier assigned by ASHRAE */
+#define BACNET_VENDOR_ID %(BACnet_Vendor_ID)s
+#define BACNET_VENDOR_NAME "%(BACnet_Vendor_Name)s"
+#define BACNET_DEVICE_MODEL_NAME "%(BACnet_Model_Name)s"
+#define BACNET_FIRMWARE_REVISION  "Beremiz BACnet Extension, BACnet Stack:" BACNET_VERSION_TEXT
+#define BACNET_DEVICE_LOCATION    "%(BACnet_Device_Location)s"
+#define BACNET_DEVICE_DESCRIPTION "%(BACnet_Device_Description)s"  
+#define BACNET_DEVICE_APPSOFT_VER "%(BACnet_Device_AppSoft_Version)s"
+
+
+/* Max number of bytes in an APDU. */
+/* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */
+/* This is used in constructing messages and to tell others our limits */
+/* 50 is the minimum; adjust to your memory and physical layer constraints */
+/* Lon=206, MS/TP=480, ARCNET=480, Ethernet=1476, BACnet/IP=1476 */
+#define MAX_APDU 1476
+/* #define MAX_APDU 128 enable this IP for testing readrange so you get the More Follows flag set */
+
+
+/* for confirmed messages, this is the number of transactions */
+/* that we hold in a queue waiting for timeout. */
+/* Configure to zero if you don't want any confirmed messages */
+/* Configure from 1..255 for number of outstanding confirmed */
+/* requests available. */
+#define MAX_TSM_TRANSACTIONS 255
+
+/* The address cache is used for binding to BACnet devices */
+/* The number of entries corresponds to the number of */
+/* devices that might respond to an I-Am on the network. */
+/* If your device is a simple server and does not need to bind, */
+/* then you don't need to use this. */
+#define MAX_ADDRESS_CACHE 255
+
+/* some modules have debugging enabled using PRINT_ENABLED */
+#define PRINT_ENABLED 0
+
+
+/* BACAPP decodes WriteProperty service requests
+   Choose the datatypes that your application supports */
+#if !(defined(BACAPP_ALL) || \
+    defined(BACAPP_NULL) || \
+    defined(BACAPP_BOOLEAN) || \
+    defined(BACAPP_UNSIGNED) || \
+    defined(BACAPP_SIGNED) || \
+    defined(BACAPP_REAL) || \
+    defined(BACAPP_DOUBLE) || \
+    defined(BACAPP_OCTET_STRING) || \
+    defined(BACAPP_CHARACTER_STRING) || \
+    defined(BACAPP_BIT_STRING) || \
+    defined(BACAPP_ENUMERATED) || \
+    defined(BACAPP_DATE) || \
+    defined(BACAPP_TIME) || \
+    defined(BACAPP_OBJECT_ID) || \
+    defined(BACAPP_DEVICE_OBJECT_PROP_REF))
+#define BACAPP_ALL
+#endif
+
+#if defined (BACAPP_ALL)
+#define BACAPP_NULL
+#define BACAPP_BOOLEAN
+#define BACAPP_UNSIGNED
+#define BACAPP_SIGNED
+#define BACAPP_REAL
+#define BACAPP_DOUBLE
+#define BACAPP_OCTET_STRING
+#define BACAPP_CHARACTER_STRING
+#define BACAPP_BIT_STRING
+#define BACAPP_ENUMERATED
+#define BACAPP_DATE
+#define BACAPP_TIME
+#define BACAPP_OBJECT_ID
+#define BACAPP_DEVICE_OBJECT_PROP_REF
+#endif
+
+/*
+** Set the maximum vector type sizes
+*/
+#define MAX_BITSTRING_BYTES        (15)
+#define MAX_CHARACTER_STRING_BYTES (MAX_APDU-6)
+#define MAX_OCTET_STRING_BYTES     (MAX_APDU-6)
+
+/*
+** Control the selection of services etc to enable code size reduction for those
+** compiler suites which do not handle removing of unused functions in modules
+** so well.
+**
+** We will start with the A type services code first as these are least likely
+** to be required in embedded systems using the stack.
+*/
+
+/*
+** Define the services that may be required.
+**
+**/
+
+/* For the moment enable them all to avoid breaking things */
+#define BACNET_SVC_I_HAVE_A    1   /* Do we send I_Have requests? */
+#define BACNET_SVC_WP_A        1   /* Do we send WriteProperty requests? */
+#define BACNET_SVC_RP_A        1   /* Do we send ReadProperty requests? */
+#define BACNET_SVC_RPM_A       1   /* Do we send ReadPropertyMultiple requests? */
+#define BACNET_SVC_DCC_A       1   /* Do we send DeviceCommunicationControl requests? */
+#define BACNET_SVC_RD_A        1   /* Do we send ReinitialiseDevice requests? */
+#define BACNET_SVC_TS_A        1
+#define BACNET_SVC_SERVER      0   /* Are we a pure server type device? */
+#define BACNET_USE_OCTETSTRING 1   /* Do we need any octet strings? */
+#define BACNET_USE_DOUBLE      1   /* Do we need any doubles? */
+#define BACNET_USE_SIGNED      1   /* Do we need any signed integers */
+
+/* Do them one by one */
+#ifndef BACNET_SVC_I_HAVE_A     /* Do we send I_Have requests? */
+#define BACNET_SVC_I_HAVE_A 0
+#endif
+
+#ifndef BACNET_SVC_WP_A /* Do we send WriteProperty requests? */
+#define BACNET_SVC_WP_A 0
+#endif
+
+#ifndef BACNET_SVC_RP_A /* Do we send ReadProperty requests? */
+#define BACNET_SVC_RP_A 0
+#endif
+
+#ifndef BACNET_SVC_RPM_A        /* Do we send ReadPropertyMultiple requests? */
+#define BACNET_SVC_RPM_A 0
+#endif
+
+#ifndef BACNET_SVC_DCC_A        /* Do we send DeviceCommunicationControl requests? */
+#define BACNET_SVC_DCC_A 0
+#endif
+
+#ifndef BACNET_SVC_RD_A /* Do we send ReinitialiseDevice requests? */
+#define BACNET_SVC_RD_A 0
+#endif
+
+#ifndef BACNET_SVC_SERVER       /* Are we a pure server type device? */
+#define BACNET_SVC_SERVER 1
+#endif
+
+#ifndef BACNET_USE_OCTETSTRING  /* Do we need any octet strings? */
+#define BACNET_USE_OCTETSTRING 0
+#endif
+
+#ifndef BACNET_USE_DOUBLE       /* Do we need any doubles? */
+#define BACNET_USE_DOUBLE 0
+#endif
+
+#ifndef BACNET_USE_SIGNED       /* Do we need any signed integers */
+#define BACNET_USE_SIGNED 0
+#endif
+
+#endif  /* CONFIG_BACNET_FOR_BEREMIZ_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/device.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,1685 @@
+/**************************************************************************
+*
+* Copyright (C) 2005,2006,2009 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/** Base "class" for handling all BACnet objects belonging
+ *  to a BACnet device, as well as Device-specific properties. */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>     /* for memmove */
+#include <time.h>       /* for timezone, localtime */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "apdu.h"
+#include "wp.h" /* WriteProperty handling */
+#include "rp.h" /* ReadProperty handling */
+#include "dcc.h"        /* DeviceCommunicationControl handling */
+#include "version.h"
+#include "device_%(locstr)s.h"     /* me */
+#include "handlers.h"
+#include "datalink.h"
+#include "address.h"
+/* os specfic includes */
+#include "timer.h"
+/* include the device object */
+#include "device_%(locstr)s.h"
+#include "ai_%(locstr)s.h"
+#include "ao_%(locstr)s.h"
+#include "av_%(locstr)s.h"
+#include "bi_%(locstr)s.h"
+#include "bo_%(locstr)s.h"
+#include "bv_%(locstr)s.h"
+#include "msi_%(locstr)s.h"
+#include "mso_%(locstr)s.h"
+#include "msv_%(locstr)s.h"
+
+
+#if defined(__BORLANDC__) || defined(_WIN32)
+/* Not included in time.h as specified by The Open Group */
+/* Difference from UTC and local standard time */
+long int timezone;
+#endif
+
+/* local forward (semi-private) and external prototypes */
+int Device_Read_Property_Local(
+    BACNET_READ_PROPERTY_DATA * rpdata);
+bool Device_Write_Property_Local(
+    BACNET_WRITE_PROPERTY_DATA * wp_data);
+extern int Routed_Device_Read_Property_Local(
+    BACNET_READ_PROPERTY_DATA * rpdata);
+extern bool Routed_Device_Write_Property_Local(
+    BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+/* may be overridden by outside table */
+static object_functions_t *Object_Table;
+
+static object_functions_t My_Object_Table[] = {
+    {OBJECT_DEVICE,
+            NULL /* Init - don't init Device or it will recurse! */ ,
+            Device_Count,
+            Device_Index_To_Instance,
+            Device_Valid_Object_Instance_Number,
+            Device_Object_Name,
+            Device_Read_Property_Local,
+            Device_Write_Property_Local,
+            Device_Property_Lists,
+            DeviceGetRRInfo,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_ANALOG_INPUT,
+            Analog_Input_Init,
+            Analog_Input_Count,
+            Analog_Input_Index_To_Instance,
+            Analog_Input_Valid_Instance,
+            Analog_Input_Object_Name,
+            Analog_Input_Read_Property,
+            Analog_Input_Write_Property,
+            Analog_Input_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_ANALOG_OUTPUT,
+            Analog_Output_Init,
+            Analog_Output_Count,
+            Analog_Output_Index_To_Instance,
+            Analog_Output_Valid_Instance,
+            Analog_Output_Object_Name,
+            Analog_Output_Read_Property,
+            Analog_Output_Write_Property,
+            Analog_Output_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_ANALOG_VALUE,
+            Analog_Value_Init,
+            Analog_Value_Count,
+            Analog_Value_Index_To_Instance,
+            Analog_Value_Valid_Instance,
+            Analog_Value_Object_Name,
+            Analog_Value_Read_Property,
+            Analog_Value_Write_Property,
+            Analog_Value_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_BINARY_INPUT,
+            Binary_Input_Init,
+            Binary_Input_Count,
+            Binary_Input_Index_To_Instance,
+            Binary_Input_Valid_Instance,
+            Binary_Input_Object_Name,
+            Binary_Input_Read_Property,
+            Binary_Input_Write_Property,
+            Binary_Input_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_BINARY_OUTPUT,
+            Binary_Output_Init,
+            Binary_Output_Count,
+            Binary_Output_Index_To_Instance,
+            Binary_Output_Valid_Instance,
+            Binary_Output_Object_Name,
+            Binary_Output_Read_Property,
+            Binary_Output_Write_Property,
+            Binary_Output_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_BINARY_VALUE,
+            Binary_Value_Init,
+            Binary_Value_Count,
+            Binary_Value_Index_To_Instance,
+            Binary_Value_Valid_Instance,
+            Binary_Value_Object_Name,
+            Binary_Value_Read_Property,
+            Binary_Value_Write_Property,
+            Binary_Value_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_MULTI_STATE_INPUT,
+            Multistate_Input_Init,
+            Multistate_Input_Count,
+            Multistate_Input_Index_To_Instance,
+            Multistate_Input_Valid_Instance,
+            Multistate_Input_Object_Name,
+            Multistate_Input_Read_Property,
+            Multistate_Input_Write_Property,
+            Multistate_Input_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_MULTI_STATE_OUTPUT,
+            Multistate_Output_Init,
+            Multistate_Output_Count,
+            Multistate_Output_Index_To_Instance,
+            Multistate_Output_Valid_Instance,
+            Multistate_Output_Object_Name,
+            Multistate_Output_Read_Property,
+            Multistate_Output_Write_Property,
+            Multistate_Output_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {OBJECT_MULTI_STATE_VALUE,
+            Multistate_Value_Init,
+            Multistate_Value_Count,
+            Multistate_Value_Index_To_Instance,
+            Multistate_Value_Valid_Instance,
+            Multistate_Value_Object_Name,
+            Multistate_Value_Read_Property,
+            Multistate_Value_Write_Property,
+            Multistate_Value_Property_Lists,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+            NULL /* Intrinsic Reporting */ },
+    {MAX_BACNET_OBJECT_TYPE,
+            NULL /* Init */ ,
+            NULL /* Count */ ,
+            NULL /* Index_To_Instance */ ,
+            NULL /* Valid_Instance */ ,
+            NULL /* Object_Name */ ,
+            NULL /* Read_Property */ ,
+            NULL /* Write_Property */ ,
+            NULL /* Property_Lists */ ,
+            NULL /* ReadRangeInfo */ ,
+            NULL /* Iterator */ ,
+            NULL /* Value_Lists */ ,
+            NULL /* COV */ ,
+            NULL /* COV Clear */ ,
+        NULL /* Intrinsic Reporting */ }
+};
+
+/** Glue function to let the Device object, when called by a handler,
+ * lookup which Object type needs to be invoked.
+ * param: Object_Type [in] The type of BACnet Object the handler wants to access.
+ * return: Pointer to the group of object helper functions that implement this
+ *         type of Object.
+ */
+static struct object_functions *Device_Objects_Find_Functions(
+    BACNET_OBJECT_TYPE Object_Type)
+{
+    struct object_functions *pObject = NULL;
+
+    pObject = Object_Table;
+    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+        /* handle each object type */
+        if (pObject->Object_Type == Object_Type) {
+            return (pObject);
+        }
+        pObject++;
+    }
+
+    return (NULL);
+}
+
+/** Try to find a rr_info_function helper function for the requested object type.
+ *
+ * param: object_type [in] The type of BACnet Object the handler wants to access.
+ * return: Pointer to the object helper function that implements the
+ *         ReadRangeInfo function, Object_RR_Info, for this type of Object on
+ *         success, else a NULL pointer if the type of Object isn't supported
+ *         or doesn't have a ReadRangeInfo function.
+ */
+rr_info_function Device_Objects_RR_Info(
+    BACNET_OBJECT_TYPE object_type)
+{
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    return (pObject != NULL ? pObject->Object_RR_Info : NULL);
+}
+
+/** For a given object type, returns the special property list.
+ * This function is used for ReadPropertyMultiple calls which want
+ * just Required, just Optional, or All properties.
+ *
+ * param: object_type [in] The desired BACNET_OBJECT_TYPE whose properties
+ *            are to be listed.
+ * param: pPropertyList [out] Reference to the structure which will, on return,
+ *            list, separately, the Required, Optional, and Proprietary object
+ *            properties with their counts.
+ */
+void Device_Objects_Property_List(
+    BACNET_OBJECT_TYPE object_type,
+    struct special_property_list_t *pPropertyList)
+{
+    struct object_functions *pObject = NULL;
+
+    pPropertyList->Required.pList = NULL;
+    pPropertyList->Optional.pList = NULL;
+    pPropertyList->Proprietary.pList = NULL;
+
+    /* If we can find an entry for the required object type
+     * and there is an Object_List_RPM fn ptr then call it
+     * to populate the pointers to the individual list counters.
+     */
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) {
+        pObject->Object_RPM_List(&pPropertyList->Required.pList,
+            &pPropertyList->Optional.pList, &pPropertyList->Proprietary.pList);
+    }
+
+    /* Fetch the counts if available otherwise zero them */
+    pPropertyList->Required.count =
+        pPropertyList->Required.pList ==
+        NULL ? 0 : property_list_count(pPropertyList->Required.pList);
+
+    pPropertyList->Optional.count =
+        pPropertyList->Optional.pList ==
+        NULL ? 0 : property_list_count(pPropertyList->Optional.pList);
+
+    pPropertyList->Proprietary.count =
+        pPropertyList->Proprietary.pList ==
+        NULL ? 0 : property_list_count(pPropertyList->Proprietary.pList);
+
+    return;
+}
+
+/** Commands a Device re-initialization, to a given state.
+ * The request's password must match for the operation to succeed.
+ * This implementation provides a framework, but doesn't
+ * actually *DO* anything.
+ * @note You could use a mix of states and passwords to multiple outcomes.
+ * @note You probably want to restart *after* the simple ack has been sent
+ *       from the return handler, so just set a local flag here.
+ *
+ * param: rd_data [in,out] The information from the RD request.
+ *                         On failure, the error class and code will be set.
+ * return: True if succeeds (password is correct), else False.
+ */
+bool Device_Reinitialize(
+    BACNET_REINITIALIZE_DEVICE_DATA * rd_data)
+{
+    bool status = false;
+
+    if (characterstring_ansi_same(&rd_data->password, "Jesus")) {
+        switch (rd_data->state) {
+            case BACNET_REINIT_COLDSTART:
+            case BACNET_REINIT_WARMSTART:
+                dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
+                break;
+            case BACNET_REINIT_STARTBACKUP:
+                break;
+            case BACNET_REINIT_ENDBACKUP:
+                break;
+            case BACNET_REINIT_STARTRESTORE:
+                break;
+            case BACNET_REINIT_ENDRESTORE:
+                break;
+            case BACNET_REINIT_ABORTRESTORE:
+                break;
+            default:
+                break;
+        }
+        /* Note: you could use a mix of state
+           and password to multiple things */
+        /* note: you probably want to restart *after* the
+           simple ack has been sent from the return handler
+           so just set a flag from here */
+        status = true;
+    } else {
+        rd_data->error_class = ERROR_CLASS_SECURITY;
+        rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE;
+    }
+
+    return status;
+}
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Device_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                           /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,                /* W  R ( 75) */
+    PROP_OBJECT_NAME,                      /* W  R ( 77) */
+    PROP_OBJECT_TYPE,                      /* R  R ( 79) */
+    PROP_SYSTEM_STATUS,                    /* R  R (112) */
+    PROP_VENDOR_NAME,                      /* R  R (121) */
+    PROP_VENDOR_IDENTIFIER,                /* W  R (120) */
+    PROP_MODEL_NAME,                       /* W  R ( 70) */
+    PROP_FIRMWARE_REVISION,                /* R  R ( 44) */
+    PROP_APPLICATION_SOFTWARE_VERSION,     /* R  R ( 12) */
+    PROP_PROTOCOL_VERSION,                 /* R  R ( 98) */
+    PROP_PROTOCOL_REVISION,                /* R  R (139) */
+    PROP_PROTOCOL_SERVICES_SUPPORTED,      /* R  R ( 97) */
+    PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED,  /* R  R ( 96) */
+    PROP_OBJECT_LIST,                      /* R  R ( 76) */
+    PROP_MAX_APDU_LENGTH_ACCEPTED,         /* R  R ( 62) */
+    PROP_SEGMENTATION_SUPPORTED,           /* R  R (107) */
+    PROP_APDU_TIMEOUT,                     /* W  R ( 11) */
+    PROP_NUMBER_OF_APDU_RETRIES,           /* W  R ( 73) */
+    PROP_DEVICE_ADDRESS_BINDING,           /* R  R ( 30) */
+    PROP_DATABASE_REVISION,                /* R  R (155) */
+//  PROP_PROPERTY_LIST,                    /* R  R (371) */
+    -1
+};
+
+static const int Device_Properties_Optional[] = {
+    PROP_DESCRIPTION,                      /* W  O ( 28) */
+    PROP_LOCAL_TIME,                       /* R  O ( 57) */
+    PROP_UTC_OFFSET,                       /* R  O (119) */
+    PROP_LOCAL_DATE,                       /* R  O ( 56) */
+    PROP_DAYLIGHT_SAVINGS_STATUS,          /* R  O ( 24) */
+    PROP_LOCATION,                         /* W  O ( 58) */
+    -1
+};
+
+static const int Device_Properties_Proprietary[] = {
+    -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Binary_Value_Init() based off the values
+ * stored in Binary_Value_Properties_Required 
+ *           Binary_Value_Properties_Optional
+ *           Binary_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Device_Properties_List[64];
+
+
+void Device_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Device_Properties_Required;
+    if (pOptional)
+        *pOptional = Device_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Device_Properties_Proprietary;
+
+    return;
+}
+
+
+static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
+
+static BACNET_CHARACTER_STRING My_Object_Name;
+static uint32_t Object_Instance_Number                             = 260001;
+static uint16_t Vendor_Identifier                                  = BACNET_VENDOR_ID;
+static char    *Vendor_Name                                        = BACNET_VENDOR_NAME;
+static char    *Firmware_Revision                                  = BACNET_FIRMWARE_REVISION;
+static char     Model_Name                  [MAX_DEV_MOD_LEN  + 1] = BACNET_DEVICE_MODEL_NAME;
+static char     Application_Software_Version[MAX_DEV_VER_LEN  + 1] = BACNET_DEVICE_APPSOFT_VER;
+static char     Location                    [MAX_DEV_LOC_LEN  + 1] = BACNET_DEVICE_LOCATION;
+static char     Description                 [MAX_DEV_DESC_LEN + 1] = BACNET_DEVICE_DESCRIPTION;
+/* static uint8_t Protocol_Version = 1; - constant, not settable */
+/* static uint8_t Protocol_Revision = 4; - constant, not settable */
+/* Protocol_Services_Supported - dynamically generated */
+/* Protocol_Object_Types_Supported - in RP encoding */
+/* Object_List - dynamically generated */
+/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */
+/* static uint8_t Max_Segments_Accepted = 0; */
+/* VT_Classes_Supported */
+/* Active_VT_Sessions */
+static BACNET_TIME Local_Time;  /* rely on OS, if there is one */
+static BACNET_DATE Local_Date;  /* rely on OS, if there is one */
+/* NOTE: BACnet UTC Offset is inverse of common practice.
+   If your UTC offset is -5hours of GMT,
+   then BACnet UTC offset is +5hours.
+   BACnet UTC offset is expressed in minutes. */
+static int32_t UTC_Offset = 5 * 60;
+static bool Daylight_Savings_Status = false;    /* rely on OS */
+/* List_Of_Session_Keys */
+/* Time_Synchronization_Recipients */
+/* Max_Master - rely on MS/TP subsystem, if there is one */
+/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */
+/* Device_Address_Binding - required, but relies on binding cache */
+static uint32_t Database_Revision = 0;
+/* Configuration_Files */
+/* Last_Restore_Time */
+/* Backup_Failure_Timeout */
+/* Active_COV_Subscriptions */
+/* Slave_Proxy_Enable */
+/* Manual_Slave_Address_Binding */
+/* Auto_Slave_Discovery */
+/* Slave_Address_Binding */
+/* Profile_Name */
+
+unsigned Device_Count(
+    void)
+{
+    return 1;
+}
+
+uint32_t Device_Index_To_Instance(
+    unsigned index)
+{
+    index = index;
+    return Object_Instance_Number;
+}
+
+/* methods to manipulate the data */
+
+/** Return the Object Instance number for our (single) Device Object.
+ * This is a key function, widely invoked by the handler code, since
+ * it provides "our" (ie, local) address.
+ * return: The Instance number used in the BACNET_OBJECT_ID for the Device.
+ */
+uint32_t Device_Object_Instance_Number(
+    void)
+{
+    return Object_Instance_Number;
+}
+
+bool Device_Set_Object_Instance_Number(
+    uint32_t object_id)
+{
+    bool status = true; /* return value */
+
+    if (object_id <= BACNET_MAX_INSTANCE) {
+        /* Make the change and update the database revision */
+        Object_Instance_Number = object_id;
+        Device_Inc_Database_Revision();
+    } else
+        status = false;
+
+    return status;
+}
+
+bool Device_Valid_Object_Instance_Number(
+    uint32_t object_id)
+{
+    return (Object_Instance_Number == object_id);
+}
+
+bool Device_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool status = false;
+
+    if (object_instance == Object_Instance_Number) {
+        status = characterstring_copy(object_name, &My_Object_Name);
+    }
+
+    return status;
+}
+
+bool Device_Set_Object_Name(
+    BACNET_CHARACTER_STRING * object_name)
+{
+    bool status = false;        /*return value */
+
+    if (!characterstring_same(&My_Object_Name, object_name)) {
+        /* Make the change and update the database revision */
+        status = characterstring_copy(&My_Object_Name, object_name);
+        Device_Inc_Database_Revision();
+    }
+
+    return status;
+}
+
+BACNET_DEVICE_STATUS Device_System_Status(
+    void)
+{
+    return System_Status;
+}
+
+int Device_Set_System_Status(
+    BACNET_DEVICE_STATUS status,
+    bool local)
+{
+    int result = 0;     /*return value - 0 = ok, -1 = bad value, -2 = not allowed */
+
+    /* We limit the options available depending on whether the source is
+     * internal or external. */
+    if (local) {
+        switch (status) {
+            case STATUS_OPERATIONAL:
+            case STATUS_OPERATIONAL_READ_ONLY:
+            case STATUS_DOWNLOAD_REQUIRED:
+            case STATUS_DOWNLOAD_IN_PROGRESS:
+            case STATUS_NON_OPERATIONAL:
+                System_Status = status;
+                break;
+
+                /* Don't support backup at present so don't allow setting */
+            case STATUS_BACKUP_IN_PROGRESS:
+                result = -2;
+                break;
+
+            default:
+                result = -1;
+                break;
+        }
+    } else {
+        switch (status) {
+                /* Allow these for the moment as a way to easily alter
+                 * overall device operation. The lack of password protection
+                 * or other authentication makes allowing writes to this
+                 * property a risky facility to provide.
+                 */
+            case STATUS_OPERATIONAL:
+            case STATUS_OPERATIONAL_READ_ONLY:
+            case STATUS_NON_OPERATIONAL:
+                System_Status = status;
+                break;
+
+                /* Don't allow outsider set this - it should probably
+                 * be set if the device config is incomplete or
+                 * corrupted or perhaps after some sort of operator
+                 * wipe operation.
+                 */
+            case STATUS_DOWNLOAD_REQUIRED:
+                /* Don't allow outsider set this - it should be set
+                 * internally at the start of a multi packet download
+                 * perhaps indirectly via PT or WF to a config file.
+                 */
+            case STATUS_DOWNLOAD_IN_PROGRESS:
+                /* Don't support backup at present so don't allow setting */
+            case STATUS_BACKUP_IN_PROGRESS:
+                result = -2;
+                break;
+
+            default:
+                result = -1;
+                break;
+        }
+    }
+
+    return (result);
+}
+
+const char *Device_Vendor_Name(
+    void)
+{
+    return Vendor_Name;
+}
+
+/** Returns the Vendor ID for this Device.
+ * See the assignments at http://www.bacnet.org/VendorID/BACnet%%20Vendor%%20IDs.htm
+ * return: The Vendor ID of this Device.
+ */
+uint16_t Device_Vendor_Identifier(
+    void)
+{
+    return Vendor_Identifier;
+}
+
+void Device_Set_Vendor_Identifier(
+    uint16_t vendor_id)
+{
+    Vendor_Identifier = vendor_id;
+}
+
+const char *Device_Model_Name(
+    void)
+{
+    return Model_Name;
+}
+
+bool Device_Set_Model_Name(
+    const char *name,
+    size_t length)
+{
+    bool status = false;        /*return value */
+
+    if (length < sizeof(Model_Name)) {
+        memmove(Model_Name, name, length);
+        Model_Name[length] = 0;
+        status = true;
+    }
+
+    return status;
+}
+
+const char *Device_Firmware_Revision(
+    void)
+{
+     return Firmware_Revision;
+}
+
+const char *Device_Application_Software_Version(
+    void)
+{
+    return Application_Software_Version;
+}
+
+bool Device_Set_Application_Software_Version(
+    const char *name,
+    size_t length)
+{
+    bool status = false;        /*return value */
+
+    if (length < sizeof(Application_Software_Version)) {
+        memmove(Application_Software_Version, name, length);
+        Application_Software_Version[length] = 0;
+        status = true;
+    }
+
+    return status;
+}
+
+const char *Device_Description(
+    void)
+{
+    return Description;
+}
+
+bool Device_Set_Description(
+    const char *name,
+    size_t length)
+{
+    bool status = false;        /*return value */
+
+    if (length < sizeof(Description)) {
+        memmove(Description, name, length);
+        Description[length] = 0;
+        status = true;
+    }
+
+    return status;
+}
+
+const char *Device_Location(
+    void)
+{
+    return Location;
+}
+
+bool Device_Set_Location(
+    const char *name,
+    size_t length)
+{
+    bool status = false;        /*return value */
+
+    if (length < sizeof(Location)) {
+        memmove(Location, name, length);
+        Location[length] = 0;
+        status = true;
+    }
+
+    return status;
+}
+
+uint8_t Device_Protocol_Version(
+    void)
+{
+    return BACNET_PROTOCOL_VERSION;
+}
+
+uint8_t Device_Protocol_Revision(
+    void)
+{
+    return BACNET_PROTOCOL_REVISION;
+}
+
+BACNET_SEGMENTATION Device_Segmentation_Supported(
+    void)
+{
+    return SEGMENTATION_NONE;
+}
+
+uint32_t Device_Database_Revision(
+    void)
+{
+    return Database_Revision;
+}
+
+void Device_Set_Database_Revision(
+    uint32_t revision)
+{
+    Database_Revision = revision;
+}
+
+/*
+ * Shortcut for incrementing database revision as this is potentially
+ * the most common operation if changing object names and ids is
+ * implemented.
+ */
+void Device_Inc_Database_Revision(
+    void)
+{
+    Database_Revision++;
+}
+
+/** Get the total count of objects supported by this Device Object.
+ * @note Since many network clients depend on the object list
+ *       for discovery, it must be consistent!
+ * return: The count of objects, for all supported Object types.
+ */
+unsigned Device_Object_List_Count(
+    void)
+{
+    unsigned count = 0; /* number of objects */
+    struct object_functions *pObject = NULL;
+
+    /* initialize the default return values */
+    pObject = Object_Table;
+    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+        if (pObject->Object_Count) {
+            count += pObject->Object_Count();
+        }
+        pObject++;
+    }
+
+    return count;
+}
+
+/** Lookup the Object at the given array index in the Device's Object List.
+ * Even though we don't keep a single linear array of objects in the Device,
+ * this method acts as though we do and works through a virtual, concatenated
+ * array of all of our object type arrays.
+ *
+ * param: array_index [in] The desired array index (1 to N)
+ * param: object_type [out] The object's type, if found.
+ * param: instance [out] The object's instance number, if found.
+ * return: True if found, else false.
+ */
+bool Device_Object_List_Identifier(
+    unsigned array_index,
+    int *object_type,
+    uint32_t * instance)
+{
+    bool status = false;
+    unsigned count = 0;
+    unsigned object_index = 0;
+    unsigned temp_index = 0;
+    struct object_functions *pObject = NULL;
+
+    /* array index zero is length - so invalid */
+    if (array_index == 0) {
+        return status;
+    }
+    object_index = array_index - 1;
+    /* initialize the default return values */
+    pObject = Object_Table;
+    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+        if (pObject->Object_Count) {
+            object_index -= count;
+            count = pObject->Object_Count();
+            if (object_index < count) {
+                /* Use the iterator function if available otherwise
+                 * look for the index to instance to get the ID */
+                if (pObject->Object_Iterator) {
+                    /* First find the first object */
+                    temp_index = pObject->Object_Iterator(~(unsigned) 0);
+                    /* Then step through the objects to find the nth */
+                    while (object_index != 0) {
+                        temp_index = pObject->Object_Iterator(temp_index);
+                        object_index--;
+                    }
+                    /* set the object_index up before falling through to next bit */
+                    object_index = temp_index;
+                }
+                if (pObject->Object_Index_To_Instance) {
+                    *object_type = pObject->Object_Type;
+                    *instance =
+                        pObject->Object_Index_To_Instance(object_index);
+                    status = true;
+                    break;
+                }
+            }
+        }
+        pObject++;
+    }
+
+    return status;
+}
+
+/** Determine if we have an object with the given object_name.
+ * If the object_type and object_instance pointers are not null,
+ * and the lookup succeeds, they will be given the resulting values.
+ * param: object_name [in] The desired Object Name to look for.
+ * param: object_type [out] The BACNET_OBJECT_TYPE of the matching Object.
+ * param: object_instance [out] The object instance number of the matching Object.
+ * return: True on success or else False if not found.
+ */
+bool Device_Valid_Object_Name(
+    BACNET_CHARACTER_STRING * object_name1,
+    int *object_type,
+    uint32_t * object_instance)
+{
+    bool found = false;
+    int type = 0;
+    uint32_t instance;
+    unsigned max_objects = 0, i = 0;
+    bool check_id = false;
+    BACNET_CHARACTER_STRING object_name2;
+    struct object_functions *pObject = NULL;
+
+    max_objects = Device_Object_List_Count();
+    for (i = 1; i <= max_objects; i++) {
+        check_id = Device_Object_List_Identifier(i, &type, &instance);
+        if (check_id) {
+            pObject = Device_Objects_Find_Functions(type);
+            if ((pObject != NULL) && (pObject->Object_Name != NULL) &&
+                (pObject->Object_Name(instance, &object_name2) &&
+                    characterstring_same(object_name1, &object_name2))) {
+                found = true;
+                if (object_type) {
+                    *object_type = type;
+                }
+                if (object_instance) {
+                    *object_instance = instance;
+                }
+                break;
+            }
+        }
+    }
+
+    return found;
+}
+
+/** Determine if we have an object of this type and instance number.
+ * param: object_type [in] The desired BACNET_OBJECT_TYPE
+ * param: object_instance [in] The object instance number to be looked up.
+ * return: True if found, else False if no such Object in this device.
+ */
+bool Device_Valid_Object_Id(
+    int object_type,
+    uint32_t object_instance)
+{
+    bool status = false;        /* return value */
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) {
+        status = pObject->Object_Valid_Instance(object_instance);
+    }
+
+    return status;
+}
+
+/** Copy a child object's object_name value, given its ID.
+ * param: object_type [in] The BACNET_OBJECT_TYPE of the child Object.
+ * param: object_instance [in] The object instance number of the child Object.
+ * param: object_name [out] The Object Name found for this child Object.
+ * return: True on success or else False if not found.
+ */
+bool Device_Object_Name_Copy(
+    BACNET_OBJECT_TYPE object_type,
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    struct object_functions *pObject = NULL;
+    bool found = false;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if ((pObject != NULL) && (pObject->Object_Name != NULL)) {
+        found = pObject->Object_Name(object_instance, object_name);
+    }
+
+    return found;
+}
+
+static void Device_Update_Current_Time(
+    void)
+{
+    struct tm *tblock = NULL;
+#if defined(_MSC_VER)
+    time_t tTemp;
+#else
+    struct timeval tv;
+#endif
+/*
+struct tm
+
+int    tm_sec   Seconds [0,60].
+int    tm_min   Minutes [0,59].
+int    tm_hour  Hour [0,23].
+int    tm_mday  Day of month [1,31].
+int    tm_mon   Month of year [0,11].
+int    tm_year  Years since 1900.
+int    tm_wday  Day of week [0,6] (Sunday =0).
+int    tm_yday  Day of year [0,365].
+int    tm_isdst Daylight Savings flag.
+*/
+#if defined(_MSC_VER)
+    time(&tTemp);
+    tblock = localtime(&tTemp);
+#else
+    if (gettimeofday(&tv, NULL) == 0) {
+        tblock = localtime(&tv.tv_sec);
+    }
+#endif
+
+    if (tblock) {
+        datetime_set_date(&Local_Date, (uint16_t) tblock->tm_year + 1900,
+            (uint8_t) tblock->tm_mon + 1, (uint8_t) tblock->tm_mday);
+#if !defined(_MSC_VER)
+        datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour,
+            (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec,
+            (uint8_t) (tv.tv_usec / 10000));
+#else
+        datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour,
+            (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, 0);
+#endif
+        if (tblock->tm_isdst) {
+            Daylight_Savings_Status = true;
+        } else {
+            Daylight_Savings_Status = false;
+        }
+        /* note: timezone is declared in <time.h> stdlib. */
+        UTC_Offset = timezone / 60;
+    } else {
+        datetime_date_wildcard_set(&Local_Date);
+        datetime_time_wildcard_set(&Local_Time);
+        Daylight_Savings_Status = false;
+    }
+}
+
+void Device_getCurrentDateTime(
+    BACNET_DATE_TIME * DateTime)
+{
+    Device_Update_Current_Time();
+
+    DateTime->date = Local_Date;
+    DateTime->time = Local_Time;
+}
+
+int32_t Device_UTC_Offset(void)
+{
+    Device_Update_Current_Time();
+
+    return UTC_Offset;
+}
+
+bool Device_Daylight_Savings_Status(void)
+{
+    return Daylight_Savings_Status;
+}
+
+/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
+   BACNET_STATUS_ABORT for abort message */
+int Device_Read_Property_Local(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int apdu_len = 0;   /* return value */
+    int len = 0;        /* apdu len intermediate value */
+    BACNET_BIT_STRING bit_string = { 0 };
+    BACNET_CHARACTER_STRING char_string = { 0 };
+    unsigned i = 0;
+    int object_type = 0;
+    uint32_t instance = 0;
+    unsigned count = 0;
+    uint8_t *apdu = NULL;
+    struct object_functions *pObject = NULL;
+    bool found = false;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0], OBJECT_DEVICE,
+                Object_Instance_Number);
+            break;
+        case PROP_OBJECT_NAME:
+            apdu_len =
+                encode_application_character_string(&apdu[0], &My_Object_Name);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE);
+            break;
+        case PROP_DESCRIPTION:
+            characterstring_init_ansi(&char_string, Description);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_SYSTEM_STATUS:
+            apdu_len = encode_application_enumerated(&apdu[0], System_Status);
+            break;
+        case PROP_VENDOR_NAME:
+            characterstring_init_ansi(&char_string, Vendor_Name);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_VENDOR_IDENTIFIER:
+            apdu_len =
+                encode_application_unsigned(&apdu[0], Vendor_Identifier);
+            break;
+        case PROP_MODEL_NAME:
+            characterstring_init_ansi(&char_string, Model_Name);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_FIRMWARE_REVISION:
+            characterstring_init_ansi(&char_string, Firmware_Revision);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_APPLICATION_SOFTWARE_VERSION:
+            characterstring_init_ansi(&char_string,
+                Application_Software_Version);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_LOCATION:
+            characterstring_init_ansi(&char_string, Location);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_LOCAL_TIME:
+            Device_Update_Current_Time();
+            apdu_len = encode_application_time(&apdu[0], &Local_Time);
+            break;
+        case PROP_UTC_OFFSET:
+            Device_Update_Current_Time();
+            apdu_len = encode_application_signed(&apdu[0], UTC_Offset);
+            break;
+        case PROP_LOCAL_DATE:
+            Device_Update_Current_Time();
+            apdu_len = encode_application_date(&apdu[0], &Local_Date);
+            break;
+        case PROP_DAYLIGHT_SAVINGS_STATUS:
+            Device_Update_Current_Time();
+            apdu_len =
+                encode_application_boolean(&apdu[0], Daylight_Savings_Status);
+            break;
+        case PROP_PROTOCOL_VERSION:
+            apdu_len =
+                encode_application_unsigned(&apdu[0],
+                Device_Protocol_Version());
+            break;
+        case PROP_PROTOCOL_REVISION:
+            apdu_len =
+                encode_application_unsigned(&apdu[0],
+                Device_Protocol_Revision());
+            break;
+        case PROP_PROTOCOL_SERVICES_SUPPORTED:
+            /* Note: list of services that are executed, not initiated. */
+            bitstring_init(&bit_string);
+            for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) {
+                /* automatic lookup based on handlers set */
+                bitstring_set_bit(&bit_string, (uint8_t) i,
+                    apdu_service_supported((BACNET_SERVICES_SUPPORTED) i));
+            }
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
+            /* Note: this is the list of objects that can be in this device,
+               not a list of objects that this device can access */
+            bitstring_init(&bit_string);
+            for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) {
+                /* initialize all the object types to not-supported */
+                bitstring_set_bit(&bit_string, (uint8_t) i, false);
+            }
+            /* set the object types with objects to supported */
+
+            pObject = Object_Table;
+            while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+                if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) {
+                    bitstring_set_bit(&bit_string, pObject->Object_Type, true);
+                }
+                pObject++;
+            }
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_OBJECT_LIST:
+            count = Device_Object_List_Count();
+            /* Array element zero is the number of objects in the list */
+            if (rpdata->array_index == 0)
+                apdu_len = encode_application_unsigned(&apdu[0], count);
+            /* if no index was specified, then try to encode the entire list */
+            /* into one packet.  Note that more than likely you will have */
+            /* to return an error if the number of encoded objects exceeds */
+            /* your maximum APDU size. */
+            else if (rpdata->array_index == BACNET_ARRAY_ALL) {
+                for (i = 1; i <= count; i++) {
+                    found =
+                        Device_Object_List_Identifier(i, &object_type,
+                        &instance);
+                    if (found) {
+                        len =
+                            encode_application_object_id(&apdu[apdu_len],
+                            object_type, instance);
+                        apdu_len += len;
+                        /* assume next one is the same size as this one */
+                        /* can we all fit into the APDU? Don't check for last entry */
+                        if ((i != count) && (apdu_len + len) >= MAX_APDU) {
+                            /* Abort response */
+                            rpdata->error_code =
+                                ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+                            apdu_len = BACNET_STATUS_ABORT;
+                            break;
+                        }
+                    } else {
+                        /* error: internal error? */
+                        rpdata->error_class = ERROR_CLASS_SERVICES;
+                        rpdata->error_code = ERROR_CODE_OTHER;
+                        apdu_len = BACNET_STATUS_ERROR;
+                        break;
+                    }
+                }
+            } else {
+                found =
+                    Device_Object_List_Identifier(rpdata->array_index,
+                    &object_type, &instance);
+                if (found) {
+                    apdu_len =
+                        encode_application_object_id(&apdu[0], object_type,
+                        instance);
+                } else {
+                    rpdata->error_class = ERROR_CLASS_PROPERTY;
+                    rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
+                    apdu_len = BACNET_STATUS_ERROR;
+                }
+            }
+            break;
+        case PROP_MAX_APDU_LENGTH_ACCEPTED:
+            apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU);
+            break;
+        case PROP_SEGMENTATION_SUPPORTED:
+            apdu_len =
+                encode_application_enumerated(&apdu[0],
+                Device_Segmentation_Supported());
+            break;
+        case PROP_APDU_TIMEOUT:
+            apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout());
+            break;
+        case PROP_NUMBER_OF_APDU_RETRIES:
+            apdu_len = encode_application_unsigned(&apdu[0], apdu_retries());
+            break;
+        case PROP_DEVICE_ADDRESS_BINDING:
+            /* FIXME: the real max apdu remaining should be passed into function */
+            apdu_len = address_list_encode(&apdu[0], MAX_APDU);
+            break;
+        case PROP_DATABASE_REVISION:
+            apdu_len =
+                encode_application_unsigned(&apdu[0], Database_Revision);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Device_Properties_List,
+//                              property_list_count(Device_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+/** Looks up the requested Object and Property, and encodes its Value in an APDU.
+ * If the Object or Property can't be found, sets the error class and code.
+ *
+ * param: rpdata [in,out] Structure with the desired Object and Property info
+ *                 on entry, and APDU message on return.
+ * return: The length of the APDU on success, else BACNET_STATUS_ERROR
+ */
+int Device_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int apdu_len = BACNET_STATUS_ERROR;
+    struct object_functions *pObject = NULL;
+
+    /* initialize the default return values */
+    rpdata->error_class = ERROR_CLASS_OBJECT;
+    rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+    pObject = Device_Objects_Find_Functions(rpdata->object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Valid_Instance &&
+            pObject->Object_Valid_Instance(rpdata->object_instance)) {
+            if (pObject->Object_Read_Property) {
+                apdu_len = pObject->Object_Read_Property(rpdata);
+            }
+        }
+    }
+
+    return apdu_len;
+}
+
+/* returns true if successful */
+bool Device_Write_Property_Local(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    int len = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+    int object_type = 0;
+    uint32_t object_instance = 0;
+    int temp;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_OBJECT_LIST) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* FIXME: len < application_data_len: more data? */
+    switch (wp_data->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_OBJECT_ID,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                if ((value.type.Object_Id.type == OBJECT_DEVICE) &&
+                    (Device_Set_Object_Instance_Number(value.type.
+                            Object_Id.instance))) {
+                    /* FIXME: we could send an I-Am broadcast to let the world know */
+                } else {
+                    status = false;
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                }
+            }
+            break;
+        case PROP_NUMBER_OF_APDU_RETRIES:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                /* FIXME: bounds check? */
+                apdu_retries_set((uint8_t) value.type.Unsigned_Int);
+            }
+            break;
+        case PROP_APDU_TIMEOUT:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                /* FIXME: bounds check? */
+                apdu_timeout_set((uint16_t) value.type.Unsigned_Int);
+            }
+            break;
+        case PROP_VENDOR_IDENTIFIER:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                /* FIXME: bounds check? */
+                Device_Set_Vendor_Identifier((uint16_t) value.
+                    type.Unsigned_Int);
+            }
+            break;
+//       case PROP_SYSTEM_STATUS:
+//           status =
+//               WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+//               &wp_data->error_class, &wp_data->error_code);
+//           if (status) {
+//               temp = Device_Set_System_Status((BACNET_DEVICE_STATUS)
+//                   value.type.Enumerated, false);
+//               if (temp != 0) {
+//                   status = false;
+//                   wp_data->error_class = ERROR_CLASS_PROPERTY;
+//                   if (temp == -1) {
+//                       wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+//                   } else {
+//                       wp_data->error_code =
+//                           ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
+//                   }
+//               }
+//           }
+//           break;
+        case PROP_OBJECT_NAME:
+            status =
+                WPValidateString(&value,
+                characterstring_capacity(&My_Object_Name), false,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                /* All the object names in a device must be unique */
+                if (Device_Valid_Object_Name(&value.type.Character_String,
+                        &object_type, &object_instance)) {
+                    if ((object_type == wp_data->object_type) &&
+                        (object_instance == wp_data->object_instance)) {
+                        /* writing same name to same object */
+                        status = true;
+                    } else {
+                        status = false;
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_DUPLICATE_NAME;
+                    }
+                } else {
+                    Device_Set_Object_Name(&value.type.Character_String);
+                }
+            }
+            break;
+        case PROP_LOCATION:
+            status =
+                WPValidateString(&value, MAX_DEV_LOC_LEN, true,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                Device_Set_Location(characterstring_value(&value.
+                        type.Character_String),
+                    characterstring_length(&value.type.Character_String));
+            }
+            break;
+
+        case PROP_DESCRIPTION:
+            status =
+                WPValidateString(&value, MAX_DEV_DESC_LEN, true,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                Device_Set_Description(characterstring_value(&value.
+                        type.Character_String),
+                    characterstring_length(&value.type.Character_String));
+            }
+            break;
+        case PROP_MODEL_NAME:
+            status =
+                WPValidateString(&value, MAX_DEV_MOD_LEN, true,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                Device_Set_Model_Name(characterstring_value(&value.
+                        type.Character_String),
+                    characterstring_length(&value.type.Character_String));
+            }
+            break;
+
+        case PROP_OBJECT_TYPE:
+        case PROP_SYSTEM_STATUS:
+        case PROP_VENDOR_NAME:
+        case PROP_FIRMWARE_REVISION:
+        case PROP_APPLICATION_SOFTWARE_VERSION:
+        case PROP_LOCAL_TIME:
+        case PROP_UTC_OFFSET:
+        case PROP_LOCAL_DATE:
+        case PROP_DAYLIGHT_SAVINGS_STATUS:
+        case PROP_PROTOCOL_VERSION:
+        case PROP_PROTOCOL_REVISION:
+        case PROP_PROTOCOL_SERVICES_SUPPORTED:
+        case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
+        case PROP_OBJECT_LIST:
+        case PROP_MAX_APDU_LENGTH_ACCEPTED:
+        case PROP_SEGMENTATION_SUPPORTED:
+        case PROP_DEVICE_ADDRESS_BINDING:
+        case PROP_DATABASE_REVISION:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+/** Looks up the requested Object and Property, and set the new Value in it,
+ *  if allowed.
+ * If the Object or Property can't be found, sets the error class and code.
+ *
+ * param: wp_data [in,out] Structure with the desired Object and Property info
+ *              and new Value on entry, and APDU message on return.
+ * return: True on success, else False if there is an error.
+ */
+bool Device_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* Ever the pessamist! */
+    struct object_functions *pObject = NULL;
+
+    /* initialize the default return values */
+    wp_data->error_class = ERROR_CLASS_OBJECT;
+    wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+    pObject = Device_Objects_Find_Functions(wp_data->object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Valid_Instance &&
+            pObject->Object_Valid_Instance(wp_data->object_instance)) {
+            if (pObject->Object_Write_Property) {
+                status = pObject->Object_Write_Property(wp_data);
+            } else {
+                wp_data->error_class = ERROR_CLASS_PROPERTY;
+                wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            }
+        } else {
+            wp_data->error_class = ERROR_CLASS_OBJECT;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+        }
+    } else {
+        wp_data->error_class = ERROR_CLASS_OBJECT;
+        wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+    }
+
+    return (status);
+}
+
+/** Looks up the requested Object, and fills the Property Value list.
+ * If the Object or Property can't be found, returns false.
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance number to be looked up.
+ * param: [out] The value list
+ * return: True if the object instance supports this feature and value changed.
+ */
+bool Device_Encode_Value_List(
+    BACNET_OBJECT_TYPE object_type,
+    uint32_t object_instance,
+    BACNET_PROPERTY_VALUE * value_list)
+{
+    bool status = false;        /* Ever the pessamist! */
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Valid_Instance &&
+            pObject->Object_Valid_Instance(object_instance)) {
+            if (pObject->Object_Value_List) {
+                status =
+                    pObject->Object_Value_List(object_instance, value_list);
+            }
+        }
+    }
+
+    return (status);
+}
+
+/** Checks the COV flag in the requested Object
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance to be looked up.
+ * return: True if the COV flag is set
+ */
+bool Device_COV(
+    BACNET_OBJECT_TYPE object_type,
+    uint32_t object_instance)
+{
+    bool status = false;        /* Ever the pessamist! */
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Valid_Instance &&
+            pObject->Object_Valid_Instance(object_instance)) {
+            if (pObject->Object_COV) {
+                status = pObject->Object_COV(object_instance);
+            }
+        }
+    }
+
+    return (status);
+}
+
+/** Clears the COV flag in the requested Object
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance to be looked up.
+ */
+void Device_COV_Clear(
+    BACNET_OBJECT_TYPE object_type,
+    uint32_t object_instance)
+{
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Valid_Instance &&
+            pObject->Object_Valid_Instance(object_instance)) {
+            if (pObject->Object_COV_Clear) {
+                pObject->Object_COV_Clear(object_instance);
+            }
+        }
+    }
+}
+
+
+/** Looks up the requested Object to see if the functionality is supported.
+ * param: [in] The object type to be looked up.
+ * return: True if the object instance supports this feature.
+ */
+bool Device_Value_List_Supported(
+    BACNET_OBJECT_TYPE object_type)
+{
+    bool status = false;        /* Ever the pessamist! */
+    struct object_functions *pObject = NULL;
+
+    pObject = Device_Objects_Find_Functions(object_type);
+    if (pObject != NULL) {
+        if (pObject->Object_Value_List) {
+            status = true;
+        }
+    }
+
+    return (status);
+}
+
+/** Initialize the Device Object.
+ Initialize the group of object helper functions for any supported Object.
+ Initialize each of the Device Object child Object instances.
+ * param: The BACnet Object Name of the bacnet server
+ */
+void Device_Init(
+    const char * Device_Object_Name)
+{
+    struct object_functions *pObject = NULL;
+
+    /* initialize the Device_Properties_List array */
+    int len = 0;
+    len += BACnet_Init_Properties_List(Device_Properties_List + len,
+                                       Device_Properties_Required);
+    len += BACnet_Init_Properties_List(Device_Properties_List + len,
+                                       Device_Properties_Optional);
+    len += BACnet_Init_Properties_List(Device_Properties_List + len,
+                                       Device_Properties_Proprietary);
+
+    characterstring_init_ansi(&My_Object_Name, Device_Object_Name);
+    Object_Table = &My_Object_Table[0]; // sets glogbal variable!
+    pObject = Object_Table;
+    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+        if (pObject->Object_Init) {
+            pObject->Object_Init();
+        }
+        pObject++;
+    }
+}
+
+bool DeviceGetRRInfo(
+    BACNET_READ_RANGE_DATA * pRequest,  /* Info on the request */
+    RR_PROP_INFO * pInfo)
+{       /* Where to put the response */
+    bool status = false;        /* return value */
+
+    switch (pRequest->object_property) {
+        case PROP_VT_CLASSES_SUPPORTED:
+        case PROP_ACTIVE_VT_SESSIONS:
+        case PROP_LIST_OF_SESSION_KEYS:
+        case PROP_TIME_SYNCHRONIZATION_RECIPIENTS:
+        case PROP_MANUAL_SLAVE_ADDRESS_BINDING:
+        case PROP_SLAVE_ADDRESS_BINDING:
+        case PROP_RESTART_NOTIFICATION_RECIPIENTS:
+        case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS:
+            pInfo->RequestTypes = RR_BY_POSITION;
+            pRequest->error_class = ERROR_CLASS_PROPERTY;
+            pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+
+        case PROP_DEVICE_ADDRESS_BINDING:
+            pInfo->RequestTypes = RR_BY_POSITION;
+            pInfo->Handler = rr_address_list_encode;
+            status = true;
+            break;
+
+        default:
+            pRequest->error_class = ERROR_CLASS_SERVICES;
+            pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+            break;
+    }
+
+    return status;
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/device.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,369 @@
+/**************************************************************************
+*
+* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/** device.h Defines functions for handling all BACnet objects belonging
+ *                 to a BACnet device, as well as Device-specific properties. */
+
+#ifndef DEVICE_H
+#define DEVICE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacenum.h"
+#include "wp.h"
+#include "rd.h"
+#include "rp.h"
+#include "rpm.h"
+#include "readrange.h"
+
+/** Called so a BACnet object can perform any necessary initialization.
+ */
+typedef void (
+    *object_init_function) (
+    void);
+
+/** Counts the number of objects of this type.
+ * return: Count of implemented objects of this type.
+ */
+typedef unsigned (
+    *object_count_function) (
+    void);
+
+/** Maps an object index position to its corresponding BACnet object instance number.
+ * param: index [in] The index of the object, in the array of objects of its type.
+ * return: The BACnet object instance number to be used in a BACNET_OBJECT_ID.
+ */
+typedef uint32_t(
+    *object_index_to_instance_function)
+        (
+    unsigned index);
+
+/** Provides the BACnet Object_Name for a given object instance of this type.
+ * param: object_instance [in] The object instance number to be looked up.
+ * param: object_name [in,out] Pointer to a character_string structure that
+ *         will hold a copy of the object name if this is a valid object_instance.
+ * return: True if the object_instance is valid and object_name has been
+ *         filled with a copy of the Object's name.
+ */
+typedef bool(
+    *object_name_function)
+        (
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name);
+
+/** Look in the table of objects of this type, and see if this is a valid
+ *  instance number.
+ * param: [in] The object instance number to be looked up.
+ * return: True if the object instance refers to a valid object of this type.
+ */
+typedef bool(
+    *object_valid_instance_function) (
+    uint32_t object_instance);
+
+/** Helper function to step through an array of objects and find either the
+ * first one or the next one of a given type. Used to step through an array
+ * of objects which is not necessarily contiguious for each type i.e. the
+ * index for the 'n'th object of a given type is not necessarily 'n'.
+ * param: [in] The index of the current object or a value of ~0 to indicate
+ * start at the beginning.
+ * return: The index of the next object of the required type or ~0 (all bits
+ * == 1) to indicate no more objects found.
+ */
+typedef unsigned (
+    *object_iterate_function) (
+    unsigned current_index);
+
+/** Look in the table of objects of this type, and get the COV Value List.
+ * param: [in] The object instance number to be looked up.
+ * param: [out] The value list
+ * return: True if the object instance supports this feature, and has changed.
+ */
+typedef bool(
+    *object_value_list_function) (
+    uint32_t object_instance,
+    BACNET_PROPERTY_VALUE * value_list);
+
+/** Look in the table of objects for this instance to see if value changed.
+ * param: [in] The object instance number to be looked up.
+ * return: True if the object instance has changed.
+ */
+typedef bool(
+    *object_cov_function) (
+    uint32_t object_instance);
+
+/** Look in the table of objects for this instance to clear the changed flag.
+ * param: [in] The object instance number to be looked up.
+ */
+typedef void (
+    *object_cov_clear_function) (
+    uint32_t object_instance);
+
+/** Intrinsic Reporting funcionality.
+ * param: [in] Object instance.
+ */
+typedef void (
+    *object_intrinsic_reporting_function) (
+    uint32_t object_instance);
+
+
+/** Defines the group of object helper functions for any supported Object.
+ * Each Object must provide some implementation of each of these helpers
+ * in order to properly support the handlers.  Eg, the ReadProperty handler
+ * handler_read_property() relies on the instance of Object_Read_Property
+ * for each Object type, or configure the function as NULL.
+ * In both appearance and operation, this group of functions acts like
+ * they are member functions of a C++ Object base class.
+ */
+typedef struct object_functions {
+    BACNET_OBJECT_TYPE Object_Type;
+    object_init_function Object_Init;
+    object_count_function Object_Count;
+    object_index_to_instance_function Object_Index_To_Instance;
+    object_valid_instance_function Object_Valid_Instance;
+    object_name_function Object_Name;
+    read_property_function Object_Read_Property;
+    write_property_function Object_Write_Property;
+    rpm_property_lists_function Object_RPM_List;
+    rr_info_function Object_RR_Info;
+    object_iterate_function Object_Iterator;
+    object_value_list_function Object_Value_List;
+    object_cov_function Object_COV;
+    object_cov_clear_function Object_COV_Clear;
+    object_intrinsic_reporting_function Object_Intrinsic_Reporting;
+} object_functions_t;
+
+/* String Lengths for Device Object properties that may be changed
+ * (written to) over the BACnet network.
+ * Maximum sizes excluding nul terminator .
+ */
+#define STRLEN_X(minlen, str)  (((minlen)>sizeof(str))?(minlen):sizeof(str))
+#define MAX_DEV_NAME_LEN STRLEN_X(32, "%(BACnet_Device_Name)s")     /* Device name         */
+#define MAX_DEV_LOC_LEN  STRLEN_X(64, BACNET_DEVICE_LOCATION)       /* Device location     */
+#define MAX_DEV_MOD_LEN  STRLEN_X(32, BACNET_DEVICE_MODEL_NAME)     /* Device model name   */
+#define MAX_DEV_VER_LEN  STRLEN_X(16, BACNET_DEVICE_APPSOFT_VER)    /* Device application software version */
+#define MAX_DEV_DESC_LEN STRLEN_X(64, BACNET_DEVICE_DESCRIPTION)    /* Device description  */
+
+/** Structure to define the Object Properties common to all Objects. */
+typedef struct commonBacObj_s {
+
+    /** The BACnet type of this object (ie, what class is this object from?).
+     * This property, of type BACnetObjectType, indicates membership in a
+     * particular object type class. Each inherited class will be of one type.
+     */
+    BACNET_OBJECT_TYPE mObject_Type;
+
+    /** The instance number for this class instance. */
+    uint32_t Object_Instance_Number;
+
+    /** Object Name; must be unique.
+     * This property, of type CharacterString, shall represent a name for
+     * the object that is unique within the BACnet Device that maintains it.
+     */
+    char Object_Name[MAX_DEV_NAME_LEN];
+
+} COMMON_BAC_OBJECT;
+
+
+/** Structure to define the Properties of Device Objects which distinguish
+ *  one instance from another.
+ *  This structure only defines fields for properties that are unique to
+ *  a given Device object.  The rest may be fixed in device.c or hard-coded
+ *  into the read-property encoding.
+ *  This may be useful for implementations which manage multiple Devices,
+ *  eg, a Gateway.
+ */
+typedef struct devObj_s {
+    /** The BACnet Device Address for this device; ->len depends on DLL type. */
+    BACNET_ADDRESS bacDevAddr;
+
+    /** Structure for the Object Properties common to all Objects. */
+    COMMON_BAC_OBJECT bacObj;
+
+    /** Device Description. */
+    char Description[MAX_DEV_DESC_LEN];
+
+    /** The upcounter that shows if the Device ID or object structure has changed. */
+    uint32_t Database_Revision;
+} DEVICE_OBJECT_DATA;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+    void Device_Init(
+        const char * Device_Object_Name);
+
+    bool Device_Reinitialize(
+        BACNET_REINITIALIZE_DEVICE_DATA * rd_data);
+
+    BACNET_REINITIALIZED_STATE Device_Reinitialized_State(
+        void);
+
+    rr_info_function Device_Objects_RR_Info(
+        BACNET_OBJECT_TYPE object_type);
+
+    void Device_getCurrentDateTime(
+        BACNET_DATE_TIME * DateTime);
+    int32_t Device_UTC_Offset(void);
+    bool Device_Daylight_Savings_Status(void);
+
+    void Device_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+    void Device_Objects_Property_List(
+        BACNET_OBJECT_TYPE object_type,
+        struct special_property_list_t *pPropertyList);
+    /* functions to support COV */
+    bool Device_Encode_Value_List(
+        BACNET_OBJECT_TYPE object_type,
+        uint32_t object_instance,
+        BACNET_PROPERTY_VALUE * value_list);
+    bool Device_Value_List_Supported(
+        BACNET_OBJECT_TYPE object_type);
+    bool Device_COV(
+        BACNET_OBJECT_TYPE object_type,
+        uint32_t object_instance);
+    void Device_COV_Clear(
+        BACNET_OBJECT_TYPE object_type,
+        uint32_t object_instance);
+
+    uint32_t Device_Object_Instance_Number(
+        void);
+    bool Device_Set_Object_Instance_Number(
+        uint32_t object_id);
+    bool Device_Valid_Object_Instance_Number(
+        uint32_t object_id);
+    unsigned Device_Object_List_Count(
+        void);
+    bool Device_Object_List_Identifier(
+        unsigned array_index,
+        int *object_type,
+        uint32_t * instance);
+
+    unsigned Device_Count(
+        void);
+    uint32_t Device_Index_To_Instance(
+        unsigned index);
+
+    bool Device_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+    bool Device_Set_Object_Name(
+        BACNET_CHARACTER_STRING * object_name);
+    /* Copy a child object name, given its ID. */
+    bool Device_Object_Name_Copy(
+        BACNET_OBJECT_TYPE object_type,
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+    bool Device_Object_Name_ANSI_Init(const char * value);
+
+    BACNET_DEVICE_STATUS Device_System_Status(
+        void);
+    int Device_Set_System_Status(
+        BACNET_DEVICE_STATUS status,
+        bool local);
+
+    const char *Device_Vendor_Name(
+        void);
+
+    uint16_t Device_Vendor_Identifier(
+        void);
+    void Device_Set_Vendor_Identifier(
+        uint16_t vendor_id);
+
+    const char *Device_Model_Name(
+        void);
+    bool Device_Set_Model_Name(
+        const char *name,
+        size_t length);
+
+    const char *Device_Firmware_Revision(
+        void);
+
+    const char *Device_Application_Software_Version(
+        void);
+    bool Device_Set_Application_Software_Version(
+        const char *name,
+        size_t length);
+
+    const char *Device_Description(
+        void);
+    bool Device_Set_Description(
+        const char *name,
+        size_t length);
+
+    const char *Device_Location(
+        void);
+    bool Device_Set_Location(
+        const char *name,
+        size_t length);
+
+    /* some stack-centric constant values - no set methods */
+    uint8_t Device_Protocol_Version(
+        void);
+    uint8_t Device_Protocol_Revision(
+        void);
+    BACNET_SEGMENTATION Device_Segmentation_Supported(
+        void);
+
+    uint32_t Device_Database_Revision(
+        void);
+    void Device_Set_Database_Revision(
+        uint32_t revision);
+    void Device_Inc_Database_Revision(
+        void);
+
+    bool Device_Valid_Object_Name(
+        BACNET_CHARACTER_STRING * object_name,
+        int *object_type,
+        uint32_t * object_instance);
+    bool Device_Valid_Object_Id(
+        int object_type,
+        uint32_t object_instance);
+
+    int Device_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+    bool Device_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    bool DeviceGetRRInfo(
+        BACNET_READ_RANGE_DATA * pRequest,      /* Info on the request */
+        RR_PROP_INFO * pInfo);  /* Where to put the information */
+
+    int Device_Read_Property_Local(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+    bool Device_Write_Property_Local(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msi.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,556 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Input Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz pluginh  */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "msi_%(locstr)s.h"
+#include "handlers.h"
+
+
+/* initial value for present_value property of each object */ 
+#define MSI_VALUE_INIT (1)
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Input objects of BACnet protocol */
+%(MSI_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Input Objects */
+#define MAX_MULTISTATE_INPUTS %(MSI_count)s
+static MULTISTATE_INPUT_DESCR MSI_Descr[MAX_MULTISTATE_INPUTS] = {
+%(MSI_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Input_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,   /* R  R ( 75) */
+    PROP_OBJECT_NAME,         /* R  R ( 77) */
+    PROP_OBJECT_TYPE,         /* R  R ( 79) */
+    PROP_PRESENT_VALUE,       /* W  R ( 85) */
+    PROP_STATUS_FLAGS,        /* R  R (111) */
+    PROP_EVENT_STATE,         /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,      /* W  R ( 81) */
+    PROP_NUMBER_OF_STATES,    /* R  R ( 74) */
+//  PROP_PROPERTY_LIST,       /* R  R (371) */
+    -1
+};
+
+static const int Multistate_Input_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                              /*(1)(2)      */
+    PROP_DESCRIPTION,         /* R  O ( 28) */
+    PROP_STATE_TEXT,          /* R  O (110) */
+    -1
+};
+
+static const int Multistate_Input_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Multistate_Input_Init() based off the values
+ * stored in Multistate_Input_Properties_Required 
+ *           Multistate_Input_Properties_Optional
+ *           Multistate_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Input_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Input_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Multistate_Input_Properties_Required;
+    if (pOptional)
+        *pOptional = Multistate_Input_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Multistate_Input_Properties_Proprietary;
+
+    return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Input_Init(
+    void)
+{
+    unsigned int i, j;
+
+    /* initialize the Multistate_Input_Properties_List array */
+    int len = 0;
+    len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+                                       Multistate_Input_Properties_Required);
+    len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+                                       Multistate_Input_Properties_Optional);
+    len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+                                       Multistate_Input_Properties_Proprietary);
+
+    /* initialize all the analog output priority arrays to NULL */
+    for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+        MSI_Descr[i].Present_Value = MSI_VALUE_INIT;
+        for (j = 0; j < MSI_Descr[i].Number_Of_States; j++) {
+            sprintf(MSI_Descr[i].State_Text[j], "State %%d", j+1);
+        }
+    }
+    return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Input_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Multistate_Input_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Multistate_Input_Instance_To_Index(object_instance) < MAX_MULTISTATE_INPUTS);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Multistate_Input_Count(void) {return MAX_MULTISTATE_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Multistate_Input_Index_To_Instance(unsigned index) {return MSI_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Input_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_MULTISTATE_INPUTS; index++)
+        if (object_instance == MSI_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_MULTISTATE_INPUTS;
+}
+
+
+
+
+
+uint32_t Multistate_Input_Present_Value(
+    uint32_t object_instance)
+{
+    uint32_t value = MSI_VALUE_INIT;
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_INPUTS)
+        value = MSI_Descr[index].Present_Value;
+
+    return value;
+}
+
+
+
+bool Multistate_Input_Present_Value_Set(
+    uint32_t object_instance,
+    uint32_t value)
+{
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index >= MAX_MULTISTATE_INPUTS)
+      return false;
+
+    if ((value == 0) || (value > MSI_Descr[index].Number_Of_States))
+      return false;
+      
+    MSI_Descr[index].Present_Value = (uint8_t) value;
+    return true;
+}
+
+
+
+
+bool Multistate_Input_Out_Of_Service(
+    uint32_t object_instance)
+{
+    bool value = false;
+    unsigned index = 0;
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_INPUTS) {
+        value = MSI_Descr[index].Out_Of_Service;
+    }
+
+    return value;
+}
+
+
+
+void Multistate_Input_Out_Of_Service_Set(
+    uint32_t object_instance,
+    bool value)
+{
+    unsigned index = 0;
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_INPUTS) {
+        MSI_Descr[index].Out_Of_Service = value;
+    }
+
+    return;
+}
+
+
+
+static char *Multistate_Input_Description(
+    uint32_t object_instance)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    char *pName = NULL; /* return value */
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_INPUTS) {
+        pName = MSI_Descr[index].Description;
+    }
+
+    return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Input_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    bool status = false;
+
+    index = Multistate_Input_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_INPUTS) {
+        status = characterstring_init_ansi(object_name, MSI_Descr[index].Object_Name);
+    }
+
+    return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Multistate_Input_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    uint32_t present_value = 0;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    object_index = Multistate_Input_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_MULTISTATE_INPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0],
+                OBJECT_MULTI_STATE_INPUT, rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Multistate_Input_Object_Name(rpdata->object_instance,
+                &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            characterstring_init_ansi(&char_string,
+                Multistate_Input_Description(rpdata->object_instance));
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0],
+                OBJECT_MULTI_STATE_INPUT);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Multistate_Input_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_unsigned(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            if (Multistate_Input_Out_Of_Service(rpdata->object_instance)) {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    true);
+            } else {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    false);
+            }
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = MSI_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_NUMBER_OF_STATES:
+            apdu_len =
+                encode_application_unsigned(&apdu[apdu_len],
+                MSI_Descr[object_index].Number_Of_States);
+            break;
+        case PROP_STATE_TEXT:
+            BACnet_encode_array(MSI_Descr[object_index].State_Text,
+                                MSI_Descr[object_index].Number_Of_States,
+                                retfalse, BACnet_encode_character_string);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Multistate_Input_Properties_List,
+//                              property_list_count(Multistate_Input_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Input_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    int len = 0;
+    unsigned object_index = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_STATE_TEXT) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Multistate_Input_Write_Property() is called
+     */
+    object_index = Multistate_Input_Instance_To_Index(wp_data->object_instance);
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (!status) {
+                wp_data->error_class = ERROR_CLASS_PROPERTY;
+                wp_data->error_code  = ERROR_CODE_INVALID_DATA_TYPE;
+                status = false; // not really necessary here.
+            } else {
+                if (!MSI_Descr[object_index].Out_Of_Service) {
+                    /* input objects can only be written to when Out_Of_Service is true! */
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code  = ERROR_CODE_WRITE_ACCESS_DENIED;
+                    status = false;
+                } else {
+                    status =
+                        Multistate_Input_Present_Value_Set
+                        (wp_data->object_instance, value.type.Unsigned_Int);
+                    if (!status) {
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code  = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                        status = false; // not really necessary here.
+                    }
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = MSI_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                MSI_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !MSI_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    MSI_Descr[object_index].Present_Value =
+                                           *(MSI_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_NUMBER_OF_STATES:
+        case PROP_DESCRIPTION:
+        case PROP_STATE_TEXT:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Multistate_Input_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSI_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(MSI_Descr[i].Located_Var_ptr) = Multistate_Input_Present_Value(MSI_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Multistate_Input_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSI_Descr[i].Out_Of_Service)
+            continue;
+
+        // Make sure local device does not set Present_Value to 0, nor higher than Number_Of_States
+        if ((*(MSI_Descr[i].Located_Var_ptr) >  MSI_Descr[i].Number_Of_States) ||
+            (*(MSI_Descr[i].Located_Var_ptr) == 0))
+            continue;
+
+        // Everything seems to be OK. Copy the value
+        MSI_Descr[i].Present_Value = *(MSI_Descr[i].Located_Var_ptr);
+    }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msi.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,109 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_INPUT_H
+#define MULTISTATE_INPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Inputs are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+    typedef struct Multistate_Input_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint8_t  Number_Of_States;
+
+        /* stores the current value */
+        uint8_t Present_Value;
+        /* Writable out-of-service allows others to manipulate our Present Value */
+        bool Out_Of_Service;
+        char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+    } MULTISTATE_INPUT_DESCR;
+
+
+    void Multistate_Input_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+
+    bool Multistate_Input_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Multistate_Input_Count(
+        void);
+    uint32_t Multistate_Input_Index_To_Instance(
+        unsigned index);
+    unsigned Multistate_Input_Instance_To_Index(
+        uint32_t instance);
+
+    int Multistate_Input_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Multistate_Input_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    /* optional API */
+    bool Multistate_Input_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    uint32_t Multistate_Input_Present_Value(
+        uint32_t object_instance);
+    bool Multistate_Input_Present_Value_Set(
+        uint32_t object_instance,
+        uint32_t value);
+
+    bool Multistate_Input_Out_Of_Service(
+        uint32_t object_instance);
+    void Multistate_Input_Out_Of_Service_Set(
+        uint32_t object_instance,
+        bool value);
+
+    void Multistate_Input_Init(
+        void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/mso.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,668 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Output Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz pluginh  */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "mso_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since multi state value objects can never take the value 0, we 
+ * use 0 as our NULL value.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MSO_VALUE_NULL (0)
+#define MSO_VALUE_IS_NULL(x)  ((x) == MSO_VALUE_NULL)
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define MSO_VALUE_RELINQUISH_DEFAULT 1
+
+
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Output objects of BACnet protocol */
+%(MSO_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Output Objects */
+#define MAX_MULTISTATE_OUTPUTS %(MSO_count)s
+static MULTISTATE_OUTPUT_DESCR MSO_Descr[MAX_MULTISTATE_OUTPUTS] = {
+%(MSO_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Output_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,       /* R  R ( 75) */
+    PROP_OBJECT_NAME,             /* R  R ( 77) */
+    PROP_OBJECT_TYPE,             /* R  R ( 79) */
+    PROP_PRESENT_VALUE,           /* W  W ( 85) */
+    PROP_STATUS_FLAGS,            /* R  R (111) */
+    PROP_EVENT_STATE,             /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,          /* W  R ( 81) */
+    PROP_NUMBER_OF_STATES,        /* R  R ( 74) */
+    PROP_PRIORITY_ARRAY,          /* R  R ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  R (104) */
+//  PROP_PROPERTY_LIST,           /* R  R (371) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  R (431) */   
+    -1
+};
+
+static const int Multistate_Output_Properties_Optional[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_DESCRIPTION,             /* R  O ( 28) */
+    PROP_STATE_TEXT,              /* R  O (110) */
+    -1
+};
+
+static const int Multistate_Output_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Multistate_Output_Init() based off the values
+ * stored in Multistate_Output_Properties_Required 
+ *           Multistate_Output_Properties_Optional
+ *           Multistate_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Output_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Output_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Multistate_Output_Properties_Required;
+    if (pOptional)
+        *pOptional = Multistate_Output_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Multistate_Output_Properties_Proprietary;
+
+    return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Output_Init(
+    void)
+{
+    unsigned int i, j;
+
+    /* initialize the Multistate_Output_Properties_List array */
+    int len = 0;
+    len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+                                       Multistate_Output_Properties_Required);
+    len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+                                       Multistate_Output_Properties_Optional);
+    len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+                                       Multistate_Output_Properties_Proprietary);
+
+    /* initialize all the analog output priority arrays to NULL */
+    for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+        for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+            MSO_Descr[i].Present_Value[j] = MSO_VALUE_NULL;
+        }
+        for (j = 0; j < MSO_Descr[i].Number_Of_States; j++) {
+            sprintf(MSO_Descr[i].State_Text[j], "State %%d", j+1);
+        }
+    }
+    return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Output_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Multistate_Output_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Multistate_Output_Instance_To_Index(object_instance) < MAX_MULTISTATE_OUTPUTS);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Multistate_Output_Count(void) {return MAX_MULTISTATE_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Multistate_Output_Index_To_Instance(unsigned index) {return MSO_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Output_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_MULTISTATE_OUTPUTS; index++)
+        if (object_instance == MSO_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_MULTISTATE_OUTPUTS;
+}
+
+
+
+
+
+uint32_t Multistate_Output_Present_Value(
+    uint32_t object_instance)
+{
+    uint32_t value = MSO_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0; /* offset from instance lookup */
+    unsigned i = 0;
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!MSO_VALUE_IS_NULL(MSO_Descr[index].Present_Value[i])) {
+                value = MSO_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+bool Multistate_Output_Present_Value_Set(
+    uint32_t object_instance,
+    uint32_t value,
+    uint8_t  priority)
+{
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index >= MAX_MULTISTATE_OUTPUTS)
+      return false;
+
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    if ((value == 0) || (value > MSO_Descr[index].Number_Of_States))
+      return false;
+      
+    priority--;
+    MSO_Descr[index].Present_Value[priority] = (uint8_t) value;
+
+    return true;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Multistate_Output_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!MSO_VALUE_IS_NULL(MSO_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+
+bool Multistate_Output_Present_Value_Relinquish(
+    uint32_t object_instance,
+    uint8_t  priority)
+{
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index >= MAX_MULTISTATE_OUTPUTS)
+      return false;
+    
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    priority--;
+    MSO_Descr[index].Present_Value[priority] = MSO_VALUE_NULL;
+
+    return true;
+}
+
+
+
+bool Multistate_Output_Out_Of_Service(
+    uint32_t object_instance)
+{
+    bool value = false;
+    unsigned index = 0;
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        value = MSO_Descr[index].Out_Of_Service;
+    }
+
+    return value;
+}
+
+
+
+void Multistate_Output_Out_Of_Service_Set(
+    uint32_t object_instance,
+    bool value)
+{
+    unsigned index = 0;
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        MSO_Descr[index].Out_Of_Service = value;
+    }
+
+    return;
+}
+
+
+
+static char *Multistate_Output_Description(
+    uint32_t object_instance)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    char *pName = NULL; /* return value */
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        pName = MSO_Descr[index].Description;
+    }
+
+    return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Output_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    bool status = false;
+
+    index = Multistate_Output_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_OUTPUTS) {
+        status = characterstring_init_ansi(object_name, MSO_Descr[index].Object_Name);
+    }
+
+    return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Multistate_Output_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    uint32_t present_value = 0;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    object_index = Multistate_Output_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_MULTISTATE_OUTPUTS) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0],
+                OBJECT_MULTI_STATE_OUTPUT, rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Multistate_Output_Object_Name(rpdata->object_instance,
+                &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            characterstring_init_ansi(&char_string,
+                Multistate_Output_Description(rpdata->object_instance));
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0],
+                OBJECT_MULTI_STATE_OUTPUT);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Multistate_Output_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_unsigned(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            if (Multistate_Output_Out_Of_Service(rpdata->object_instance)) {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    true);
+            } else {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    false);
+            }
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = MSO_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(MSO_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                MSO_VALUE_IS_NULL,
+                                encode_application_unsigned);
+            break;
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Multistate_Output_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+
+        case PROP_RELINQUISH_DEFAULT:
+            present_value = MSO_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_unsigned(&apdu[0], present_value);
+            break;
+        case PROP_NUMBER_OF_STATES:
+            apdu_len =
+                encode_application_unsigned(&apdu[apdu_len],
+                MSO_Descr[object_index].Number_Of_States);
+            break;
+        case PROP_STATE_TEXT:
+            BACnet_encode_array(MSO_Descr[object_index].State_Text,
+                                MSO_Descr[object_index].Number_Of_States,
+                                retfalse, BACnet_encode_character_string);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Multistate_Output_Properties_List,
+//                              property_list_count(Multistate_Output_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+        (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Output_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    int len = 0;
+    unsigned object_index = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_STATE_TEXT) &&
+        (wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Multistate_Output_Write_Property() is called
+     */
+    object_index = Multistate_Output_Instance_To_Index(wp_data->object_instance);
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                status =
+                    Multistate_Output_Present_Value_Set
+                    (wp_data->object_instance, value.type.Unsigned_Int, wp_data->priority);
+                if (!status) {
+                    if (wp_data->priority == 6) {
+                        /* Command priority 6 is reserved for use by Minimum On/Off
+                           algorithm and may not be used for other purposes in any
+                           object. */
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                    } else {
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                    }
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    status =
+                        Multistate_Output_Present_Value_Relinquish
+                        (wp_data->object_instance, wp_data->priority);
+                }
+                if (!status) {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = MSO_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                MSO_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !MSO_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    MSO_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+                                           *(MSO_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_NUMBER_OF_STATES:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+        case PROP_RELINQUISH_DEFAULT:
+        case PROP_DESCRIPTION:
+        case PROP_STATE_TEXT:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Multistate_Output_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSO_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(MSO_Descr[i].Located_Var_ptr) = Multistate_Output_Present_Value(MSO_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Multistate_Output_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSO_Descr[i].Out_Of_Service)
+            continue;
+
+        // Make sure local device does not set Present_Value to a value higher than Number_Of_States
+        /* NOTE: The following comparison (Present_Value > Number_Of_States) is OK as the
+         *       MSO_VALUE_NULL is currently set to 0. This means that the local controller
+         *       can safely relinquish control by setting the Present_Value to 0.
+         *       The comparison would not be safe if for some reason we later change
+         *       MSO_VALUE_NULL to a value that may be higher than Number_Of_States.
+         */
+        #if MSO_VALUE_NULL != 0
+        #error Implementation assumes that MSO_VALUE_NULL is set to 0, which is currently not the case.
+        #endif
+        if (*(MSO_Descr[i].Located_Var_ptr) > MSO_Descr[i].Number_Of_States)
+            continue;
+
+        // Everything seems to be OK. Copy the value
+        MSO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(MSO_Descr[i].Located_Var_ptr);
+    }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/mso.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,110 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_OUTPUT_H
+#define MULTISTATE_OUTPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Outputs are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+    typedef struct Multistate_Output_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint8_t  Number_Of_States;
+
+        /* stores the current value */
+        /* one entry per priority value */
+        uint8_t Present_Value[BACNET_MAX_PRIORITY];
+        /* Writable out-of-service allows others to manipulate our Present Value */
+        bool Out_Of_Service;
+        char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+    } MULTISTATE_OUTPUT_DESCR;
+
+
+    void Multistate_Output_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+
+    bool Multistate_Output_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Multistate_Output_Count(
+        void);
+    uint32_t Multistate_Output_Index_To_Instance(
+        unsigned index);
+    unsigned Multistate_Output_Instance_To_Index(
+        uint32_t instance);
+
+    int Multistate_Output_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Multistate_Output_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    /* optional API */
+    bool Multistate_Output_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    uint32_t Multistate_Output_Present_Value(
+        uint32_t object_instance);
+    bool Multistate_Output_Present_Value_Set(
+        uint32_t object_instance,
+        uint32_t value,
+        uint8_t  priority);
+
+    bool Multistate_Output_Out_Of_Service(
+        uint32_t object_instance);
+    void Multistate_Output_Out_Of_Service_Set(
+        uint32_t object_instance,
+        bool value);
+
+    void Multistate_Output_Init(
+        void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msv.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,666 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Value Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz pluginh  */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "msv_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value.  When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since multi state value objects can never take the value 0, we 
+ * use 0 as our NULL value.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MSV_VALUE_NULL (0)
+#define MSV_VALUE_IS_NULL(x)  ((x) == MSV_VALUE_NULL)
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define MSV_VALUE_RELINQUISH_DEFAULT 1
+
+
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Value objects of BACnet protocol */
+%(MSV_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Value Objects */
+#define MAX_MULTISTATE_VALUES %(MSV_count)s
+static MULTISTATE_VALUE_DESCR MSV_Descr[MAX_MULTISTATE_VALUES] = {
+%(MSV_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the 
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Value_Properties_Required[] = {
+ /* (1) Currently Supported                  */
+ /* (2) Required by standard ASHRAE 135-2016 */
+                                  /*(1)(2)      */
+    PROP_OBJECT_IDENTIFIER,       /* R  R ( 75) */
+    PROP_OBJECT_NAME,             /* R  R ( 77) */
+    PROP_OBJECT_TYPE,             /* R  R ( 79) */
+    PROP_PRESENT_VALUE,           /* W  R ( 85) */
+    PROP_STATUS_FLAGS,            /* R  R (111) */
+    PROP_EVENT_STATE,             /* R  R ( 36) */
+    PROP_OUT_OF_SERVICE,          /* W  R ( 81) */
+    PROP_NUMBER_OF_STATES,        /* R  R ( 74) */
+//  PROP_PROPERTY_LIST,           /* R  R (371) */
+    -1
+};
+
+static const int Multistate_Value_Properties_Optional[] = {
+    PROP_DESCRIPTION,             /* R  O ( 28) */
+    PROP_STATE_TEXT,              /* R  O (110) */
+    /* required if Present_Value is writable (which is true in our case!) */
+    PROP_PRIORITY_ARRAY,          /* R  O ( 87) */
+    PROP_RELINQUISH_DEFAULT,      /* R  O (104) */
+//  PROP_CURRENT_COMMAND_PRIORITY,/* R  O (431) */   
+    -1
+};
+
+static const int Multistate_Value_Properties_Proprietary[] = {
+    -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ * 
+ * It is initialized by Multistate_Value_Init() based off the values
+ * stored in Multistate_Value_Properties_Required 
+ *           Multistate_Value_Properties_Optional
+ *           Multistate_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Value_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/**                  Callback functions.               **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Value_Property_Lists(
+    const int **pRequired,
+    const int **pOptional,
+    const int **pProprietary)
+{
+    if (pRequired)
+        *pRequired = Multistate_Value_Properties_Required;
+    if (pOptional)
+        *pOptional = Multistate_Value_Properties_Optional;
+    if (pProprietary)
+        *pProprietary = Multistate_Value_Properties_Proprietary;
+
+    return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+void Multistate_Value_Init(
+    void)
+{
+    unsigned int i, j;
+
+    /* initialize the Multistate_Value_Properties_List array */
+    int len = 0;
+    len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+                                       Multistate_Value_Properties_Required);
+    len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+                                       Multistate_Value_Properties_Optional);
+    len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+                                       Multistate_Value_Properties_Proprietary);
+
+    /* initialize all the analog output priority arrays to NULL */
+    for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+        for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+            MSV_Descr[i].Present_Value[j] = MSV_VALUE_NULL;
+        }
+        for (j = 0; j < MSV_Descr[i].Number_Of_States; j++) {
+            sprintf(MSV_Descr[i].State_Text[j], "State %%d", j+1);
+        }
+    }
+    return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Value_Valid_Instance(
+    uint32_t object_instance)
+{
+    // fprintf(stderr, "BACnet plugin: Multistate_Value_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+    return (Multistate_Value_Instance_To_Index(object_instance) < MAX_MULTISTATE_VALUES);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+unsigned Multistate_Value_Count(void) {return MAX_MULTISTATE_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+uint32_t Multistate_Value_Index_To_Instance(unsigned index) {return MSV_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Value_Instance_To_Index(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+  
+    for (index = 0; index < MAX_MULTISTATE_VALUES; index++)
+        if (object_instance == MSV_Descr[index].Object_Identifier)
+            return index;
+
+    /* error, this object ID is not in our list! */
+    return MAX_MULTISTATE_VALUES;
+}
+
+
+
+
+
+uint32_t Multistate_Value_Present_Value(
+    uint32_t object_instance)
+{
+    uint32_t value = MSV_VALUE_RELINQUISH_DEFAULT;
+    unsigned index = 0; /* offset from instance lookup */
+    unsigned i = 0;
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!MSV_VALUE_IS_NULL(MSV_Descr[index].Present_Value[i])) {
+                value = MSV_Descr[index].Present_Value[i];
+                break;
+            }
+        }
+    }
+
+    return value;
+}
+
+
+
+bool Multistate_Value_Present_Value_Set(
+    uint32_t object_instance,
+    uint32_t value,
+    uint8_t  priority)
+{
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index >= MAX_MULTISTATE_VALUES)
+      return false;
+
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    if ((value == 0) || (value > MSV_Descr[index].Number_Of_States))
+      return false;
+      
+    priority--;
+    MSV_Descr[index].Present_Value[priority] = (uint8_t) value;
+
+    return true;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Multistate_Value_Current_Command_Priority(
+    uint32_t object_instance)
+{
+    unsigned index = 0;
+    unsigned i = 0;
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+            if (!MSV_VALUE_IS_NULL(MSV_Descr[index].Present_Value[i])) {
+                return i+1; // +1 since priority is 1..16, and not 0..15
+            }
+        }
+    }
+    // command values in all priorities are set to NULL
+    return 0;
+}
+
+
+
+
+bool Multistate_Value_Present_Value_Relinquish(
+    uint32_t object_instance,
+    uint8_t  priority)
+{
+    unsigned index = 0; /* offset from instance lookup */
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index >= MAX_MULTISTATE_VALUES)
+      return false;
+    
+    if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+        (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+      return false;
+    
+    priority--;
+    MSV_Descr[index].Present_Value[priority] = MSV_VALUE_NULL;
+
+    return true;
+}
+
+
+
+bool Multistate_Value_Out_Of_Service(
+    uint32_t object_instance)
+{
+    bool value = false;
+    unsigned index = 0;
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        value = MSV_Descr[index].Out_Of_Service;
+    }
+
+    return value;
+}
+
+
+
+void Multistate_Value_Out_Of_Service_Set(
+    uint32_t object_instance,
+    bool value)
+{
+    unsigned index = 0;
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        MSV_Descr[index].Out_Of_Service = value;
+    }
+
+    return;
+}
+
+
+
+static char *Multistate_Value_Description(
+    uint32_t object_instance)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    char *pName = NULL; /* return value */
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        pName = MSV_Descr[index].Description;
+    }
+
+    return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Value_Object_Name(
+    uint32_t object_instance,
+    BACNET_CHARACTER_STRING * object_name)
+{
+    unsigned index = 0; /* offset from instance lookup */
+    bool status = false;
+
+    index = Multistate_Value_Instance_To_Index(object_instance);
+    if (index < MAX_MULTISTATE_VALUES) {
+        status = characterstring_init_ansi(object_name, MSV_Descr[index].Object_Name);
+    }
+
+    return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+int Multistate_Value_Read_Property(
+    BACNET_READ_PROPERTY_DATA * rpdata)
+{
+    int len = 0;
+    int apdu_len = 0;   /* return value */
+    BACNET_BIT_STRING bit_string;
+    BACNET_CHARACTER_STRING char_string;
+    uint32_t present_value = 0;
+    unsigned object_index = 0;
+    unsigned i = 0;
+    bool state = false;
+    uint8_t *apdu = NULL;
+
+    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+        (rpdata->application_data_len == 0)) {
+        return 0;
+    }
+
+    object_index = Multistate_Value_Instance_To_Index(rpdata->object_instance);
+    if (object_index >= MAX_MULTISTATE_VALUES) {
+        rpdata->error_class = ERROR_CLASS_OBJECT;
+        rpdata->error_code  = ERROR_CODE_UNKNOWN_OBJECT;
+        return BACNET_STATUS_ERROR;
+    }
+
+    apdu = rpdata->application_data;
+    switch (rpdata->object_property) {
+        case PROP_OBJECT_IDENTIFIER:
+            apdu_len =
+                encode_application_object_id(&apdu[0],
+                OBJECT_MULTI_STATE_VALUE, rpdata->object_instance);
+            break;
+        case PROP_OBJECT_NAME:
+            Multistate_Value_Object_Name(rpdata->object_instance,
+                &char_string);
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_DESCRIPTION:
+            characterstring_init_ansi(&char_string,
+                Multistate_Value_Description(rpdata->object_instance));
+            apdu_len =
+                encode_application_character_string(&apdu[0], &char_string);
+            break;
+        case PROP_OBJECT_TYPE:
+            apdu_len =
+                encode_application_enumerated(&apdu[0],
+                OBJECT_MULTI_STATE_VALUE);
+            break;
+        case PROP_PRESENT_VALUE:
+            present_value =
+                Multistate_Value_Present_Value(rpdata->object_instance);
+            apdu_len = encode_application_unsigned(&apdu[0], present_value);
+            break;
+        case PROP_STATUS_FLAGS:
+            /* note: see the details in the standard on how to use these */
+            bitstring_init(&bit_string);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+            bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+            if (Multistate_Value_Out_Of_Service(rpdata->object_instance)) {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    true);
+            } else {
+                bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+                    false);
+            }
+            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+            break;
+        case PROP_EVENT_STATE:
+            /* note: see the details in the standard on how to use this */
+            apdu_len =
+                encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+            break;
+        case PROP_OUT_OF_SERVICE:
+            state = MSV_Descr[object_index].Out_Of_Service;
+            apdu_len = encode_application_boolean(&apdu[0], state);
+            break;
+        case PROP_PRIORITY_ARRAY:
+            BACnet_encode_array(MSV_Descr[object_index].Present_Value,
+                                BACNET_MAX_PRIORITY,
+                                MSV_VALUE_IS_NULL,
+                                encode_application_unsigned);
+            break;
+//      case PROP_CURRENT_COMMAND_PRIORITY: {
+//          unsigned i = Multistate_Value_Current_Command_Priority(rpdata->object_instance);
+//          if (i == 0)  apdu_len = encode_application_null    (&apdu[0]);
+//          else         apdu_len = encode_application_unsigned(&apdu[0], i);
+//          break;
+//      }
+        case PROP_RELINQUISH_DEFAULT:
+            present_value = MSV_VALUE_RELINQUISH_DEFAULT;
+            apdu_len = encode_application_unsigned(&apdu[0], present_value);
+            break;
+        case PROP_NUMBER_OF_STATES:
+            apdu_len =
+                encode_application_unsigned(&apdu[apdu_len],
+                MSV_Descr[object_index].Number_Of_States);
+            break;
+        case PROP_STATE_TEXT:
+            BACnet_encode_array(MSV_Descr[object_index].State_Text,
+                                MSV_Descr[object_index].Number_Of_States,
+                                retfalse, BACnet_encode_character_string);
+            break;
+//      case PROP_PROPERTY_LIST:
+//          BACnet_encode_array(Multistate_Value_Properties_List,
+//                              property_list_count(Multistate_Value_Properties_List),
+//                              retfalse, encode_application_enumerated);
+//          break;
+        default:
+            rpdata->error_class = ERROR_CLASS_PROPERTY;
+            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            apdu_len = BACNET_STATUS_ERROR;
+            break;
+    }
+    /*  only array properties can have array options */
+    if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+        (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+//      (rpdata->object_property != PROP_PROPERTY_LIST) &&
+        (rpdata->array_index != BACNET_ARRAY_ALL)) {
+        rpdata->error_class = ERROR_CLASS_PROPERTY;
+        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        apdu_len = BACNET_STATUS_ERROR;
+    }
+
+    return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c,  */
+bool Multistate_Value_Write_Property(
+    BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+    bool status = false;        /* return value */
+    int len = 0;
+    unsigned object_index = 0;
+    BACNET_APPLICATION_DATA_VALUE value;
+
+    /* decode the some of the request */
+    len =
+        bacapp_decode_application_data(wp_data->application_data,
+        wp_data->application_data_len, &value);
+    /* FIXME: len < application_data_len: more data? */
+    if (len < 0) {
+        /* error while decoding - a value larger than we can handle */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+        return false;
+    }
+    if ((wp_data->object_property != PROP_STATE_TEXT) &&
+        (wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+        (wp_data->array_index != BACNET_ARRAY_ALL)) {
+        /*  only array properties can have array options */
+        wp_data->error_class = ERROR_CLASS_PROPERTY;
+        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+        return false;
+    }
+    /* No need to check whether object_index is within bounds.
+     * Has already been checked before Multistate_Output_Write_Property() is called
+     */
+    object_index = Multistate_Value_Instance_To_Index(wp_data->object_instance);
+
+    switch (wp_data->object_property) {
+        case PROP_PRESENT_VALUE:
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                status =
+                    Multistate_Value_Present_Value_Set
+                    (wp_data->object_instance, value.type.Unsigned_Int, wp_data->priority);
+                if (!status) {
+                    if (wp_data->priority == 6) {
+                        /* Command priority 6 is reserved for use by Minimum On/Off
+                           algorithm and may not be used for other purposes in any
+                           object. */
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+                    } else {
+                        wp_data->error_class = ERROR_CLASS_PROPERTY;
+                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+                    }
+                }
+            } else {
+                status =
+                    WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+                    &wp_data->error_class, &wp_data->error_code);
+                if (status) {
+                    status =
+                        Multistate_Value_Present_Value_Relinquish
+                        (wp_data->object_instance, wp_data->priority);
+                }
+                if (!status) {
+                    wp_data->error_class = ERROR_CLASS_PROPERTY;
+                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+                }
+            }
+            break;
+        case PROP_OUT_OF_SERVICE:
+        {
+            bool Previous_Out_Of_Service = MSV_Descr[object_index].Out_Of_Service;
+            status =
+                WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+                &wp_data->error_class, &wp_data->error_code);
+            if (status) {
+                MSV_Descr[object_index].Out_Of_Service = value.type.Boolean;
+                if (Previous_Out_Of_Service && !MSV_Descr[object_index].Out_Of_Service)
+                    /* We have just changed from Out_of_Service -> In Service */
+                    /* We need to update the Present_Value to the value
+                     * currently in the PLC...
+                     */
+                    MSV_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+                                           *(MSV_Descr[object_index].Located_Var_ptr);
+            }
+            break;
+        }
+        case PROP_OBJECT_IDENTIFIER:
+        case PROP_OBJECT_NAME:
+        case PROP_OBJECT_TYPE:
+        case PROP_STATUS_FLAGS:
+        case PROP_EVENT_STATE:
+        case PROP_NUMBER_OF_STATES:
+        case PROP_PRIORITY_ARRAY:
+//      case PROP_CURRENT_COMMAND_PRIORITY:
+        case PROP_RELINQUISH_DEFAULT:
+        case PROP_DESCRIPTION:
+        case PROP_STATE_TEXT:
+//      case PROP_PROPERTY_LIST:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+            break;
+        default:
+            wp_data->error_class = ERROR_CLASS_PROPERTY;
+            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+            break;
+    }
+
+    return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin  **/
+/********************************************/
+
+
+
+void  Multistate_Value_Copy_Present_Value_to_Located_Var(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        *(MSV_Descr[i].Located_Var_ptr) = Multistate_Value_Present_Value(MSV_Descr[i].Object_Identifier);
+    }
+}
+
+
+
+void  Multistate_Value_Copy_Located_Var_to_Present_Value(void) {
+    unsigned i;
+    for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+        // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+        if (MSV_Descr[i].Out_Of_Service)
+            continue;
+
+        // copy the value
+        // Make sure local device does not set Present_Value to a value higher than Number_Of_States
+        /* NOTE: The following comparison (Present_Value > Number_Of_States) is OK as the
+         *       MSV_VALUE_NULL is currently set to 0. This means that the local controller
+         *       can safely relinquish control by setting the Present_Value to 0.
+         *       The comparison would not be safe if for some reason we later change
+         *       MSV_VALUE_NULL to a value that may be higher than Number_Of_States.
+         */
+        #if MSV_VALUE_NULL != 0
+        #error Implementation assumes that MSV_VALUE_NULL is set to 0, which is currently not the case.
+        #endif
+        if (*(MSV_Descr[i].Located_Var_ptr) > MSV_Descr[i].Number_Of_States)
+            continue;
+
+        // Everything seems to be OK. Copy the value
+        MSV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(MSV_Descr[i].Located_Var_ptr);
+    }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msv.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,110 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_VALUE_H
+#define MULTISTATE_VALUE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Values are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+    typedef struct multistate_value_descr {
+        /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+        uint8_t *Located_Var_ptr;
+        uint32_t Object_Identifier;  /* called object 'Instance' in the source code! */
+        char    *Object_Name;   
+        char    *Description;
+        uint8_t  Number_Of_States;
+
+        /* stores the current value */
+        /* one entry per priority value */
+        uint8_t Present_Value[BACNET_MAX_PRIORITY];
+        /* Writable out-of-service allows others to manipulate our Present Value */
+        bool Out_Of_Service;
+        char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+    } MULTISTATE_VALUE_DESCR;
+
+
+    void Multistate_Value_Property_Lists(
+        const int **pRequired,
+        const int **pOptional,
+        const int **pProprietary);
+
+    bool Multistate_Value_Valid_Instance(
+        uint32_t object_instance);
+    unsigned Multistate_Value_Count(
+        void);
+    uint32_t Multistate_Value_Index_To_Instance(
+        unsigned index);
+    unsigned Multistate_Value_Instance_To_Index(
+        uint32_t instance);
+
+    int Multistate_Value_Read_Property(
+        BACNET_READ_PROPERTY_DATA * rpdata);
+
+    bool Multistate_Value_Write_Property(
+        BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+    /* optional API */
+    bool Multistate_Value_Object_Name(
+        uint32_t object_instance,
+        BACNET_CHARACTER_STRING * object_name);
+
+    uint32_t Multistate_Value_Present_Value(
+        uint32_t object_instance);
+    bool Multistate_Value_Present_Value_Set(
+        uint32_t object_instance,
+        uint32_t value,
+        uint8_t  priority);
+
+    bool Multistate_Value_Out_Of_Service(
+        uint32_t object_instance);
+    void Multistate_Value_Out_Of_Service_Set(
+        uint32_t object_instance,
+        bool value);
+
+    void Multistate_Value_Init(
+        void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/server.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,645 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <inttypes.h>  // uint32_t, ..., PRIu32, ...
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz pluginh  */
+#include "server_%(locstr)s.h"
+#include "address.h"
+#include "bacdef.h"
+#include "handlers.h"
+#include "client.h"
+#include "dlenv.h"
+#include "bacdcode.h"
+#include "npdu.h"
+#include "apdu.h"
+#include "iam.h"
+#include "tsm.h"
+#include "datalink.h"
+#include "dcc.h"
+#include "getevent.h"
+#include "net.h"
+#include "txbuf.h"
+#include "version.h"
+#include "timesync.h"
+
+
+/* A utility function used by most (all?) implementations of BACnet Objects */
+/* Adds to Prop_List all entries in Prop_List_XX that are not
+ * PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PROPERTY_LIST
+ * and returns the number of elements that were added
+ */
+int BACnet_Init_Properties_List(
+          int *Prop_List, 
+    const int *Prop_List_XX) {
+  
+    unsigned int i = 0;
+    unsigned int j = 0;
+    
+    for (j = 0; Prop_List_XX[j] >= 0; j++)
+        // Add any propety, except for the following 4 which should not be included
+        // in the Property_List property array.
+        //  (see ASHRAE 135-2016, for example section 12.4.34)
+        if ((Prop_List_XX[j] != PROP_OBJECT_IDENTIFIER) &&
+            (Prop_List_XX[j] != PROP_OBJECT_NAME)       &&
+            (Prop_List_XX[j] != PROP_OBJECT_TYPE)       &&
+            (Prop_List_XX[j] != PROP_PROPERTY_LIST)) {
+            Prop_List[i] = Prop_List_XX[j];
+            i++;
+        }
+    Prop_List[i] = -1; // marks the end of the list!
+    return i;
+}
+
+
+
+
+
+
+int BACnet_encode_character_string(uint8_t *apdu, const char *str) {
+    BACNET_CHARACTER_STRING   char_string;
+    characterstring_init_ansi(&char_string, str);
+    /* FIXME: this might go beyond MAX_APDU length! */
+    return encode_application_character_string(apdu, &char_string);
+}
+
+/* macro that always returns false.
+ * To be used as the 'test_null' parameter to the BACnet_encode_array macro
+ * in situations where we should never encode_null() values.
+ */
+#define retfalse(x) (false)
+
+#define BACnet_encode_array(array, array_len, test_null, encode_function)                  \
+{                                                                                           \
+    uint8_t *apdu = NULL;                                                                   \
+    apdu = rpdata->application_data;                                                        \
+                                                                                            \
+    switch (rpdata->array_index) {                                                          \
+      case 0: /* Array element zero is the number of elements in the array */               \
+        apdu_len = encode_application_unsigned(&apdu[0], array_len);                        \
+        break;                                                                              \
+      case BACNET_ARRAY_ALL: {                                                              \
+        /* if no index was specified, then try to encode the entire list */                 \
+        unsigned i = 0;                                                                     \
+        apdu_len   = 0;                                                                     \
+        for (i = 0; i < array_len; i++) {                                                   \
+            /* FIXME: this might go beyond MAX_APDU length! */                              \
+            if (!test_null(array[i]))                                                       \
+                  apdu_len += encode_function        (&apdu[apdu_len], array[i]);           \
+            else  apdu_len += encode_application_null(&apdu[apdu_len]);                     \
+            /* return error if it does not fit in the APDU */                               \
+            if (apdu_len >= MAX_APDU) {                                                     \
+                rpdata->error_class = ERROR_CLASS_SERVICES;                                 \
+                rpdata->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;                        \
+                apdu_len = BACNET_STATUS_ERROR;                                             \
+                break; /* for(;;) */                                                        \
+            }                                                                               \
+        }                                                                                   \
+        break;                                                                              \
+      }                                                                                     \
+      default:                                                                              \
+        if (rpdata->array_index <= array_len) {                                             \
+            if (!test_null(array[rpdata->array_index - 1]))                                 \
+                  apdu_len += encode_function(&apdu[0], array[rpdata->array_index - 1]);    \
+            else  apdu_len += encode_application_null(&apdu[0]);                            \
+        } else {                                                                            \
+            rpdata->error_class = ERROR_CLASS_PROPERTY;                                     \
+            rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;                            \
+            apdu_len = BACNET_STATUS_ERROR;                                                 \
+        }                                                                                   \
+        break;                                                                              \
+    } /* switch() */                                                                        \
+}
+
+
+/* include the device object */
+#include "device_%(locstr)s.c"
+#include "ai_%(locstr)s.c"
+#include "ao_%(locstr)s.c"
+#include "av_%(locstr)s.c"
+#include "bi_%(locstr)s.c"
+#include "bo_%(locstr)s.c"
+#include "bv_%(locstr)s.c"
+#include "msi_%(locstr)s.c"
+#include "mso_%(locstr)s.c"
+#include "msv_%(locstr)s.c"
+
+
+/** Buffer used for receiving */
+static uint8_t Rx_Buf[MAX_MPDU] = { 0 };
+
+
+
+
+/*******************************************************/
+/* BACnet Service Handlers taylored to Beremiz plugin  */ 
+/*******************************************************/
+
+static void BACNET_show_date_time(
+    BACNET_DATE * bdate,
+    BACNET_TIME * btime)
+{
+    /* show the date received */
+    fprintf(stderr, "%%u", (unsigned) bdate->year);
+    fprintf(stderr, "/%%u", (unsigned) bdate->month);
+    fprintf(stderr, "/%%u", (unsigned) bdate->day);
+    /* show the time received */
+    fprintf(stderr, " %%02u", (unsigned) btime->hour);
+    fprintf(stderr, ":%%02u", (unsigned) btime->min);
+    fprintf(stderr, ":%%02u", (unsigned) btime->sec);
+    fprintf(stderr, ".%%02u", (unsigned) btime->hundredths);
+    fprintf(stderr, "\r\n");
+}
+
+static time_t __timegm(struct tm *new_time) {
+    time_t     sec =  mktime(new_time);  /* assume new_time is in local time */
+    
+    /* sec will be an aproximation of the correct value. 
+     * We must now fix this with the current difference
+     * between UTC and localtime.
+     * 
+     * WARNING: The following algorithm to determine the current
+     *          difference between local time and UTC will not 
+     *          work if each value (lcl and utc) falls on a different
+     *          side of a change to/from DST.
+     *          For example, assume a change to DST is made at 12h00 
+     *          of day X. The following algorithm does not work if:
+     *            - lcl falls before 12h00 of day X
+     *            - utc falls after  12h00 of day X
+     */
+    struct  tm lcl = *localtime(&sec); // lcl will be == new_time
+    struct  tm utc = *gmtime   (&sec);
+    
+    if (lcl.tm_isdst == 1)  utc.tm_isdst = 1;
+    
+    time_t sec_lcl =  mktime(&lcl);
+    time_t sec_utc =  mktime(&utc);
+
+    /* difference in seconds between localtime and utc */
+    time_t sec_dif = sec_lcl - sec_utc;
+    return sec + sec_dif;
+}
+
+
+static void BACNET_set_date_time(
+    BACNET_DATE * bdate,
+    BACNET_TIME * btime,
+    int utc /* set to > 0 if date & time in UTC */)
+{
+    struct tm       brokendown_time;
+    time_t          seconds = 0;
+    struct timespec ts;
+
+    brokendown_time.tm_sec   = btime->sec;        /* seconds 0..60    */
+    brokendown_time.tm_min   = btime->min;        /* minutes 0..59    */
+    brokendown_time.tm_hour  = btime->hour;       /* hours   0..23    */
+    brokendown_time.tm_mday  = bdate->day;        /* day of the month 1..31 */
+    brokendown_time.tm_mon   = bdate->month-1;    /* month 0..11      */
+    brokendown_time.tm_year  = bdate->year-1900;  /* years since 1900 */
+//  brokendown_time.tm_wday  = ;                  /* day of the week  */
+//  brokendown_time.tm_yday  = ;                  /* day in the year  */
+    brokendown_time.tm_isdst = -1;                /* daylight saving time (-1 => unknown) */
+
+    // Tranform time into format -> 'seconds since epoch'
+    /* WARNING: timegm() is a non standard GNU extension.
+     *          If you do not have it on your build system then consider
+     *          finding the source code for timegm() (it is LGPL) from GNU
+     *          C library and copying it here 
+     *          (e.g. https://code.woboq.org/userspace/glibc/time/timegm.c.html)
+     *          Another alternative is to use the fundion __timegm() above,
+     *          which will mostly work but may have errors when the time being 
+     *          converted is close to the time in the year when changing 
+     *          to/from DST (daylight saving time)
+     */
+    if (utc > 0) seconds = timegm(&brokendown_time);    
+    else         seconds = mktime(&brokendown_time);
+
+    ts.tv_sec  = seconds;
+    ts.tv_nsec = btime->hundredths*10*1000*1000;
+
+//  fprintf(stderr, "clock_settime() s=%%ul,  ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+    clock_settime(CLOCK_REALTIME, &ts); 
+//  clock_gettime(CLOCK_REALTIME, &ts); 
+//  fprintf(stderr, "clock_gettime() s=%%ul,  ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+}
+
+
+
+void BACnet_handler_timesync(
+    uint8_t * service_request,
+    uint16_t service_len,
+    BACNET_ADDRESS * src)
+{
+    int len = 0;
+    BACNET_DATE bdate;
+    BACNET_TIME btime;
+
+    (void) src;
+    (void) service_len;
+    len =
+        timesync_decode_service_request(service_request, service_len, &bdate, &btime);
+    if (len > 0) {
+        fprintf(stderr, "BACnet plugin: Received TimeSyncronization Request -> ");
+        BACNET_show_date_time(&bdate, &btime);
+        /* set the time */
+        BACNET_set_date_time(&bdate, &btime, 0 /* time in local time */);
+    }
+
+    return;
+}
+
+void BACnet_handler_timesync_utc(
+    uint8_t * service_request,
+    uint16_t service_len,
+    BACNET_ADDRESS * src)
+{
+    int len = 0;
+    BACNET_DATE bdate;
+    BACNET_TIME btime;
+
+    (void) src;
+    (void) service_len;
+    len =
+        timesync_decode_service_request(service_request, service_len, &bdate, &btime);
+    if (len > 0) {
+        fprintf(stderr, "BACnet plugin: Received TimeSyncronizationUTC Request -> ");
+        BACNET_show_date_time(&bdate, &btime);
+        /* set the time */
+        BACNET_set_date_time(&bdate, &btime, 1 /* time in UTC */);
+    }
+
+    return;
+}
+
+
+
+/**********************************************/
+/** Initialize the handlers we will utilize. **/
+/**********************************************/
+/*
+ * TLDR: The functions that will create the __Resp.__ messages.
+ * 
+ * The service handlers are the functions that will respond to BACnet requests this device receives.
+ * In essence, the service handlers will create and send the Resp. (Response) messages
+ * of the Req. -> Ind. -> Resp. -> Conf. service sequence defined in OSI
+ * (Request, Indication, Response, Confirmation)
+ * 
+ */
+static int Init_Service_Handlers(
+    void)
+{
+    /* set the handler for all the services we don't implement */
+    /* It is required to send the proper reject message... */
+    apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
+
+    /* Set the handlers for any unconfirmed services that we support. */
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS,  // DM-DDB-B - Dynamic Device Binding B (Resp.)
+                                   handler_who_is);           //            (see ASHRAE 135-2016, section K5.1 and K5.2)
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM,    // DM-DDB-A - Dynamic Device Binding A (Resp.)
+//                                 handler_i_am_bind);        //            Responding to I_AM requests is for clients (A)!
+//                                                            //            (see ASHRAE 135-2016, section K5.1 and K5.2)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, // DM-DOB-B - Dynamic Object Binding B (Resp.)
+                                   handler_who_has);          //            (see ASHRAE 135-2016, section K5.3 and K5.4)
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_HAVE,  // DM-DOB-A - Dynamic Object Binding A (Resp.)
+//                                 handler_i_have);           //            Responding to I_HAVE requests is for clients (A)!
+//                                                            //            (see ASHRAE 135-2016, section K5.3 and K5.4)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, // DM-UTC-B -UTCTimeSynchronization-B (Resp.)
+                                 BACnet_handler_timesync_utc);                 //            (see ASHRAE 135-2016, section K5.14)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION,     // DM-TS-B - TimeSynchronization-B (Resp.)
+                                 BACnet_handler_timesync);                     //            (see ASHRAE 135-2016, section K5.12)
+
+    /* Set the handlers for any confirmed services that we support. */
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY,      // DS-RP-B - Read Property B (Resp.)
+                                 handler_read_property);             //            (see ASHRAE 135-2016, section K1.2)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, // DS-RPM-B -Read Property Multiple-B (Resp.)
+//                               handler_read_property_multiple);    //            (see ASHRAE 135-2016, section K1.4)
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY,     // DS-WP-B - Write Property B (Resp.)
+                                 handler_write_property);            //            (see ASHRAE 135-2016, section K1.8)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE,// DS-WPM-B -Write Property Multiple B (Resp.)
+//                               handler_write_property_multiple);   //            (see ASHRAE 135-2016, section K1.10)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE,
+//                               handler_read_range);
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE,
+//                               handler_reinitialize_device);
+    
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV,
+//                               handler_cov_subscribe);
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_COV_NOTIFICATION,
+//                               handler_ucov_notification);
+    /* handle communication so we can shutup when asked */
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, // DM-DCC-B - Device Communication Control B
+                                 handler_device_communication_control);        //            (see ASHRAE 135-2016, section K5.6)
+//  /* handle the data coming back from private requests */
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER,
+//                               handler_unconfirmed_private_transfer);
+    
+    // success
+    return 0;
+}
+
+
+
+static int Init_Network_Interface(
+    const char *interface,    // for linux:   /dev/eth0, /dev/eth1, /dev/wlan0, ...
+                              // for windows: 192.168.0.1 (IP addr. of interface)
+                              // NULL => use default!
+    const char *port,         // Port the server will listen on. (NULL => use default)
+    const char *apdu_timeout, // (NULL => use default)
+    const char *apdu_retries  // (NULL => use default)
+   )
+{
+    char datalink_layer[4];
+
+    strcpy(datalink_layer, "BIP"); // datalink_set() does not accpet const char *, so we copy it...
+//  BIP_Debug = true;
+
+    if (port != NULL) {
+        bip_set_port(htons((uint16_t) strtol(port, NULL, 0)));
+    } else {
+            bip_set_port(htons(0xBAC0));
+    }
+    
+    if (apdu_timeout != NULL) {
+        apdu_timeout_set((uint16_t) strtol(apdu_timeout, NULL, 0));
+    }
+
+    if (apdu_retries != NULL) {
+        apdu_retries_set((uint8_t) strtol(apdu_retries, NULL, 0));
+    }
+
+    // datalink_init is a pointer that will actually call bip_init()
+    // datalink_init pointer is set by the call datalink_set("BIP")
+    /* NOTE: current implementation of BACnet stack uses the interface
+     *       only to determine the server's local IP address and broacast address.
+     *       The local IP addr is later used to discard (broadcast) messages
+     *       it receives that were sent by itself.
+     *       The broadcast IP addr is used for broadcast messages.
+     *       WARNING: The socket itself is created to listen on all 
+     *       available interfaces (INADDR_ANY), so this setting may induce
+     *       the user in error as we will accept messages arriving on other
+     *       interfaces (if they exist)
+     *       (see bip_init() in ports/linux/bip-init.c)
+     *       (see bip_****() in src/bip.c)
+     */
+    char *tmp = (char *)malloc(strlen(interface) + 1);
+    if (tmp == NULL) return -1;
+    strncpy(tmp, interface, strlen(interface) + 1);
+    if (!datalink_init(tmp)) {
+        return -1;
+    }
+// #if (MAX_TSM_TRANSACTIONS)
+//     pEnv = getenv("BACNET_INVOKE_ID");
+//     if (pEnv) {
+//         tsm_invokeID_set((uint8_t) strtol(pEnv, NULL, 0));
+//     }
+// #endif
+    dlenv_register_as_foreign_device();
+    
+    // success
+    return 0;
+}
+
+
+
+/** Main function of server demo. **/
+int bn_server_run(server_node_t *server_node) {
+    int res = 0;
+    BACNET_ADDRESS src = {0};  /* address where message came from */
+    uint16_t pdu_len = 0;
+    unsigned timeout = 1000;       /* milliseconds */
+    time_t last_seconds = 0;
+    time_t current_seconds = 0;
+    uint32_t elapsed_seconds = 0;
+    uint32_t elapsed_milliseconds = 0;
+    uint32_t address_binding_tmr = 0;
+    uint32_t recipient_scan_tmr = 0;
+
+    /* allow the device ID to be set */
+    Device_Set_Object_Instance_Number(server_node->device_id);
+    /* load any static address bindings in our device bindings list */
+    address_init();
+    /* load any entries in the BDT table from backup file */
+    bvlc_bdt_restore_local();
+    /* Initiliaze the bacnet server 'device' */    
+    Device_Init(server_node->device_name);
+    /* Set the password (max 31 chars) for Device Communication Control request. */
+    /* Default in the BACnet stack is hardcoded as "filister" */
+    /* (char *) cast is to remove the cast. The function is incorrectly declared/defined in the BACnet stack! */
+    /* BACnet stack needs to change demo/handler/h_dcc.c and include/handlers.h                               */
+    handler_dcc_password_set((char *)server_node->comm_control_passwd);
+    /* Set callbacks and configure network interface */
+    res = Init_Service_Handlers();
+    if (res < 0) exit(1);
+    res = Init_Network_Interface(
+            server_node->network_interface, // interface    (NULL => use default (eth0))
+            server_node->port_number,       // Port number  (NULL => use default (0xBAC0))
+            NULL,              // apdu_timeout (NULL => use default)
+            NULL               // apdu_retries (NULL => use default)
+           );
+    if (res < 0) {
+        fprintf(stderr, "BACnet plugin: error initializing bacnet server node %%s!\n", server_node->location);
+        exit(1); // kill the server thread!
+    }
+    /* BACnet stack correcly configured. Give user some feedback! */
+    struct in_addr my_addr, broadcast_addr;
+    my_addr.       s_addr = bip_get_addr();
+    broadcast_addr.s_addr = bip_get_broadcast_addr();
+    printf("BACnet plugin:"
+                         " Local IP addr: %%s" 
+                        ", Broadcast IP addr: %%s" 
+                        ", Port number: 0x%%04X [%%hu]" 
+                        ", BACnet Device ID: %%d"
+                        ", Max APDU: %%d"
+                        ", BACnet Stack Version %%s\n",
+                        inet_ntoa(my_addr),
+                        inet_ntoa(broadcast_addr),
+                        ntohs(bip_get_port()), ntohs(bip_get_port()),
+                        Device_Object_Instance_Number(), 
+                        MAX_APDU,
+                        Device_Firmware_Revision()
+                        );
+    /* configure the timeout values */
+    last_seconds = time(NULL);
+    /* broadcast an I-Am on startup */
+    Send_I_Am(&Handler_Transmit_Buffer[0]);
+    /* loop forever */
+    for (;;) {
+        /* input */
+        current_seconds = time(NULL);
+
+        /* returns 0 bytes on timeout */
+        pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout);
+
+        /* process */
+        if (pdu_len) {
+            npdu_handler(&src, &Rx_Buf[0], pdu_len);
+        }
+        /* at least one second has passed */
+        elapsed_seconds = (uint32_t) (current_seconds - last_seconds);
+        if (elapsed_seconds) {
+            last_seconds = current_seconds;
+            dcc_timer_seconds(elapsed_seconds);
+            bvlc_maintenance_timer(elapsed_seconds);
+            dlenv_maintenance_timer(elapsed_seconds);
+            elapsed_milliseconds = elapsed_seconds * 1000;
+            tsm_timer_milliseconds(elapsed_milliseconds);
+        }
+        handler_cov_task();
+        /* scan cache address */
+        address_binding_tmr += elapsed_seconds;
+        if (address_binding_tmr >= 60) {
+            address_cache_timer(address_binding_tmr);
+            address_binding_tmr = 0;
+        }
+    }
+
+    /* should never occur!! */
+    return 0;
+}
+
+
+
+
+
+#include <pthread.h>
+
+static void *__bn_server_thread(void *_server_node)  {
+	server_node_t *server_node = _server_node;
+
+	// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
+	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+	// bn_server_run() should never return!
+	bn_server_run(server_node);
+	fprintf(stderr, "BACnet plugin: bacnet server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */
+	return NULL;
+}
+
+
+int __cleanup_%(locstr)s ();
+
+int __init_%(locstr)s (int argc, char **argv){
+	int index;
+
+	/* init each local server */
+	/* NOTE: All server_nodes[].init_state are initialised to 0 in the code 
+	 *       generated by the BACnet plugin 
+	 */
+	/* create the BACnet server */
+	server_node.init_state = 1; // we have created the node
+	
+	/* launch a thread to handle this server node */
+	{
+		int res = 0;
+		pthread_attr_t attr;
+		res |= pthread_attr_init(&attr);
+		res |= pthread_create(&(server_node.thread_id), &attr, &__bn_server_thread, (void *)&(server_node));
+		if (res !=  0) {
+			fprintf(stderr, "BACnet plugin: Error starting bacnet server thread for node %%s\n", server_node.location);
+			goto error_exit;
+		}
+	}
+	server_node.init_state = 2; // we have created the node and thread
+
+	return 0;
+	
+error_exit:
+	__cleanup_%(locstr)s ();
+	return -1;
+}
+
+
+
+
+
+void __publish_%(locstr)s (){
+       Analog_Value_Copy_Located_Var_to_Present_Value();
+       Analog_Input_Copy_Located_Var_to_Present_Value();
+      Analog_Output_Copy_Located_Var_to_Present_Value();
+       Binary_Value_Copy_Located_Var_to_Present_Value();
+       Binary_Input_Copy_Located_Var_to_Present_Value();
+      Binary_Output_Copy_Located_Var_to_Present_Value();
+   Multistate_Value_Copy_Located_Var_to_Present_Value();
+   Multistate_Input_Copy_Located_Var_to_Present_Value();
+  Multistate_Output_Copy_Located_Var_to_Present_Value();
+}
+
+
+
+
+
+void __retrieve_%(locstr)s (){
+       Analog_Value_Copy_Present_Value_to_Located_Var();
+       Analog_Input_Copy_Present_Value_to_Located_Var();
+      Analog_Output_Copy_Present_Value_to_Located_Var();
+       Binary_Value_Copy_Present_Value_to_Located_Var();
+       Binary_Input_Copy_Present_Value_to_Located_Var();
+      Binary_Output_Copy_Present_Value_to_Located_Var();
+   Multistate_Value_Copy_Present_Value_to_Located_Var();
+   Multistate_Input_Copy_Present_Value_to_Located_Var();
+  Multistate_Output_Copy_Present_Value_to_Located_Var();
+}
+
+
+
+
+
+int __cleanup_%(locstr)s (){
+	int index, close;
+	int res = 0;
+
+	/* kill thread and close connections of each modbus server node */
+	close = 0;
+	if (server_node.init_state >= 2) {
+		// thread was launched, so we try to cancel it!
+		close  = pthread_cancel(server_node.thread_id);
+		close |= pthread_join  (server_node.thread_id, NULL);
+		if (close < 0)
+			fprintf(stderr, "BACnet plugin: Error closing thread for bacnet server %%s\n", server_node.location);
+	}
+	res |= close;
+
+	close = 0;
+	if (server_node.init_state >= 1) {
+		// bacnet server node was created, so we try to close it!
+		// datalink_cleanup is a pointer that will actually call bip_cleanup()
+		// datalink_cleanup pointer is set by the call datalink_set("BIP")
+		datalink_cleanup();
+	}
+	res |= close;
+	server_node.init_state = 0;
+
+	/* bacnet library close */
+	// Nothing to do ???
+
+	return res;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/server.h	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,61 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+#ifndef SERVER_H_
+#define SERVER_H_
+
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+
+typedef struct{
+	    const char *location;
+	    const char *network_interface;
+	    const char *port_number;
+	    const char *device_name;
+	    const char *comm_control_passwd;
+	    uint32_t	device_id; // device ID is 22 bits long! uint16_t is not enough!
+	    int		init_state; // store how far along the server's initialization has progressed
+	    pthread_t	thread_id;  // thread handling this server
+	} server_node_t;
+
+
+
+/*initialization following all parameters given by user in application*/
+static server_node_t server_node = {
+  "%(locstr)s",
+  "%(network_interface)s",           // interface    (NULL => use default (eth0))
+  "%(port_number)s",                 // Port number  (NULL => use default)
+  "%(BACnet_Device_Name)s",          // BACnet server's device (object) name
+  "%(BACnet_Comm_Control_Password)s",// BACnet server's device (object) name
+   %(BACnet_Device_ID)s              // BACnet server's device (object) ID
+};
+
+
+
+#endif /* SERVER_H_ */
--- a/features.py	Fri Jun 08 09:43:48 2018 +0200
+++ b/features.py	Fri Jun 08 13:28:00 2018 +0200
@@ -29,6 +29,7 @@
 
 catalog = [
     ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'),
+    ('bacnet', _('Bacnet support'), _('Map located variables over Bacnet'), 'bacnet.bacnet.RootClass'),
     ('modbus', _('Modbus support'), _('Map located variables over Modbus'), 'modbus.modbus.RootClass'),
     ('c_ext', _('C extension'), _('Add C code accessing located variables synchronously'), 'c_ext.CFile'),
     ('py_ext', _('Python file'), _('Add Python code executed asynchronously'), 'py_ext.PythonFile'),