# HG changeset patch # User Edouard Tisserant # Date 1528457280 -7200 # Node ID 6dddf3070806f9599ca481b57da814376e42c0e7 # Parent 92f02bb17c7ed7463075c159b4e3db7fbc2f12aa Add BACnet extension from Mario de Sousa diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/BacnetSlaveEditor.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() diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/__init__.py --- /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 . +# +# 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 * diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/bacnet.py --- /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 . +# +# 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 = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + # 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 = """ + # + # + # + # + #""" + #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug, "Bacnet Slave") + ##,("XXX",_XXXXPlug, "XXX") + #] + + #def CTNGenerate_C(self, buildpath, locations): + #return [], "", True diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/ede_files/template_EDE.csv --- /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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/ede_files/template_ObjTypes.csv --- /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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/ede_files/template_StateTexts.csv --- /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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/ede_files/template_Units.csv --- /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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/ai.c --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include +#include +#include /* 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); + } +} + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/ai.h --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/ao.c --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include +#include +#include /* 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); + } +} + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/ao.h --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/av.c --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include +#include +#include /* 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); + } +} + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/av.h --- /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 +* Copyright (C) 2011 Krzysztof Malorny +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bi.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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; + } +} + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bi.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bo.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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; + } +} + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bo.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bv.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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; + } +} + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/bv.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/config_bacnet_for_beremiz.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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: + * --> --> + * + * 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, 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 */ +#ifndef BIG_ENDIAN +#include +# ifdef BYTE_ORDER +# if BYTE_ORDER == LITTLE_ENDIAN +/*# warning "Using to determine platform endianness."*/ +# undef BIG_ENDIAN +# define BIG_ENDIAN 0 +# elif BYTE_ORDER == BIG_ENDIAN +/*# warning "Using 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 */ diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/device.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include /* for memmove */ +#include /* 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 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; +} + + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/device.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/msi.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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); + } +} + + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/msi.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/mso.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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); + } +} + + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/mso.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/msv.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include + +#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); + } +} + + + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/msv.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#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 diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/server.c --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include +#include +#include +#include +#include +#include // 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 + +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; +} + diff -r 92f02bb17c7e -r 6dddf3070806 bacnet/runtime/server.h --- /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 +* Copyright (C) 2017 Mario de Sousa +* +* 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 +#include + + + +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_ */ diff -r 92f02bb17c7e -r 6dddf3070806 features.py --- 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'),