Merged #2486, enhancements to WAMP client : auto reconnecting, wamp conf checking and saving, additional attributes for exposed calls, etc
--- a/Beremiz.py Tue Jun 05 15:29:58 2018 +0200
+++ b/Beremiz.py Wed Jul 04 14:17:00 2018 +0200
@@ -48,6 +48,7 @@
self.splashPath = self.Bpath("images", "splash.png")
self.modules = ["BeremizIDE"]
self.debug = os.path.exists("BEREMIZ_DEBUG")
+ self.handle_exception = None
def Bpath(self, *args):
return os.path.join(self.app_dir, *args)
@@ -115,9 +116,13 @@
def ShowSplashScreen(self):
class Splash(AdvancedSplash):
+ Painted = False
+
def OnPaint(_self, event): # pylint: disable=no-self-argument
AdvancedSplash.OnPaint(_self, event)
- wx.CallAfter(self.AppStart)
+ if not _self.Painted: # trigger app start only once
+ _self.Painted = True
+ wx.CallAfter(self.AppStart)
bmp = wx.Image(self.splashPath).ConvertToBitmap()
self.splash = Splash(None,
bitmap=bmp,
@@ -198,10 +203,13 @@
self.CreateUI()
self.CloseSplash()
self.ShowUI()
- # except (KeyboardInterrupt, SystemExit):
- # raise
+ except (KeyboardInterrupt, SystemExit):
+ raise
except Exception:
- self.handle_exception(*sys.exc_info(), exit=True)
+ if self.handle_exception is not None:
+ self.handle_exception(*sys.exc_info(), exit=True)
+ else:
+ raise
def MainLoop(self):
self.app.MainLoop()
--- a/Beremiz_service.py Tue Jun 05 15:29:58 2018 +0200
+++ b/Beremiz_service.py Wed Jul 04 14:17:00 2018 +0200
@@ -30,12 +30,14 @@
import sys
import getopt
import threading
-from threading import Thread, currentThread, Semaphore
+from threading import Thread, currentThread, Semaphore, Lock
import traceback
import __builtin__
+import Pyro
import Pyro.core as pyro
-from runtime import PLCObject, ServicePublisher
+from runtime import PLCObject, ServicePublisher, MainWorker
+from runtime.xenomai import TryPreloadXenomai
import util.paths as paths
@@ -132,6 +134,8 @@
if __name__ == '__main__':
__builtin__.__dict__['_'] = lambda x: x
+ # TODO: add a cmdline parameter if Trying Preloading Xenomai makes problem
+ TryPreloadXenomai()
def Bpath(*args):
@@ -401,7 +405,7 @@
class Server(object):
def __init__(self, servicename, ip_addr, port,
- workdir, argv, autostart=False,
+ workdir, argv,
statuschange=None, evaluator=default_evaluator,
pyruntimevars=None):
self.continueloop = True
@@ -411,22 +415,48 @@
self.port = port
self.workdir = workdir
self.argv = argv
- self.plcobj = None
self.servicepublisher = None
- self.autostart = autostart
self.statuschange = statuschange
self.evaluator = evaluator
self.pyruntimevars = pyruntimevars
-
- def Loop(self):
+ self.plcobj = PLCObject(self)
+
+ def _to_be_published(self):
+ return self.servicename is not None and \
+ self.ip_addr is not None and \
+ self.ip_addr != "localhost" and \
+ self.ip_addr != "127.0.0.1"
+
+ def PrintServerInfo(self):
+ print(_("Pyro port :"), self.port)
+
+ # Beremiz IDE detects LOCAL:// runtime is ready by looking
+ # for self.workdir in the daemon's stdout.
+ print(_("Current working directory :"), self.workdir)
+
+ if self._to_be_published():
+ print(_("Publishing service on local network"))
+
+ sys.stdout.flush()
+
+ def PyroLoop(self, when_ready):
while self.continueloop:
+ Pyro.config.PYRO_MULTITHREADED = 0
pyro.initServer()
self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
+
# pyro never frees memory after connection close if no timeout set
# taking too small timeout value may cause
# unwanted diconnection when IDE is kept busy for long periods
self.daemon.setTimeout(60)
- self.Start()
+
+ self.daemon.connect(self.plcobj, "PLCObject")
+
+ if self._to_be_published():
+ self.servicepublisher = ServicePublisher.ServicePublisher()
+ self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
+
+ when_ready()
self.daemon.requestLoop()
self.daemon.sock.close()
@@ -440,39 +470,6 @@
self.plcobj.UnLoadPLC()
self._stop()
- def Start(self):
- self.plcobj = PLCObject(self.workdir, self.daemon, self.argv,
- self.statuschange, self.evaluator,
- self.pyruntimevars)
-
- uri = self.daemon.connect(self.plcobj, "PLCObject")
-
- print(_("Pyro port :"), self.port)
- print(_("Pyro object's uri :"), uri)
-
- # Beremiz IDE detects daemon start by looking
- # for self.workdir in the daemon's stdout.
- # Therefore don't delete the following line
- print(_("Current working directory :"), self.workdir)
-
- # Configure and publish service
- # Not publish service if localhost in address params
- if self.servicename is not None and \
- self.ip_addr is not None and \
- self.ip_addr != "localhost" and \
- self.ip_addr != "127.0.0.1":
- print(_("Publishing service on local network"))
- self.servicepublisher = ServicePublisher.ServicePublisher()
- self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
-
- self.plcobj.AutoLoad()
- if self.plcobj.GetPLCstatus()[0] != "Empty":
- if self.autostart:
- self.plcobj.StartPLC()
- self.plcobj.StatusChange()
-
- sys.stdout.flush()
-
def _stop(self):
if self.plcobj is not None:
self.plcobj.StopPLC()
@@ -481,6 +478,13 @@
self.servicepublisher = None
self.daemon.shutdown(True)
+ def AutoLoad(self):
+ self.plcobj.AutoLoad()
+ if self.plcobj.GetPLCstatus()[0] == "Stopped":
+ if autostart:
+ self.plcobj.StartPLC()
+ self.plcobj.StatusChange()
+
if enabletwisted:
import warnings
@@ -506,7 +510,7 @@
if havewx:
wx_eval_lock = Semaphore(0)
- main_thread = currentThread()
+ # main_thread = currentThread()
def statuschangeTskBar(status):
wx.CallAfter(taskbar_instance.UpdateIcon, status)
@@ -519,23 +523,23 @@
wx_eval_lock.release()
def evaluator(tocall, *args, **kwargs):
- if main_thread == currentThread():
- # avoid dead lock if called from the wx mainloop
- return default_evaluator(tocall, *args, **kwargs)
- else:
- o = type('', (object,), dict(call=(tocall, args, kwargs), res=None))
- wx.CallAfter(wx_evaluator, o)
- wx_eval_lock.acquire()
- return o.res
+ # if main_thread == currentThread():
+ # # avoid dead lock if called from the wx mainloop
+ # return default_evaluator(tocall, *args, **kwargs)
+ # else:
+ o = type('', (object,), dict(call=(tocall, args, kwargs), res=None))
+ wx.CallAfter(wx_evaluator, o)
+ wx_eval_lock.acquire()
+ return o.res
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
+ WorkingDir, argv,
statuschange, evaluator, pyruntimevars)
taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
else:
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
+ WorkingDir, argv,
statuschange, pyruntimevars=pyruntimevars)
@@ -612,6 +616,7 @@
try:
website = NS.RegisterWebsite(webport)
pyruntimevars["website"] = website
+ NS.SetServer(pyroserver)
statuschange.append(NS.website_statuslistener_factory(website))
except Exception:
LogMessageAndException(_("Nevow Web service failed. "))
@@ -630,19 +635,46 @@
except Exception:
LogMessageAndException(_("WAMP client startup failed. "))
+pyro_thread_started = Lock()
+pyro_thread_started.acquire()
+pyro_thread = Thread(target=pyroserver.PyroLoop,
+ kwargs=dict(when_ready=pyro_thread_started.release))
+pyro_thread.start()
+
+# Wait for pyro thread to be effective
+pyro_thread_started.acquire()
+
+pyroserver.PrintServerInfo()
if havetwisted or havewx:
- pyro_thread = Thread(target=pyroserver.Loop)
- pyro_thread.start()
-
+ ui_thread_started = Lock()
+ ui_thread_started.acquire()
if havetwisted:
- reactor.run()
- elif havewx:
- app.MainLoop()
-else:
- try:
- pyroserver.Loop()
- except KeyboardInterrupt:
- pass
+ # reactor._installSignalHandlersAgain()
+ def ui_thread_target():
+ # FIXME: had to disable SignaHandlers install because
+ # signal not working in non-main thread
+ reactor.run(installSignalHandlers=False)
+ else:
+ ui_thread_target = app.MainLoop
+
+ ui_thread = Thread(target=ui_thread_target)
+ ui_thread.start()
+
+ # This order ui loop to unblock main thread when ready.
+ if havetwisted:
+ reactor.callLater(0, ui_thread_started.release)
+ else:
+ wx.CallAfter(ui_thread_started.release)
+
+ # Wait for ui thread to be effective
+ ui_thread_started.acquire()
+ print("UI thread started successfully.")
+
+try:
+ MainWorker.runloop(pyroserver.AutoLoad)
+except KeyboardInterrupt:
+ pass
+
pyroserver.Quit()
sys.exit(0)
--- a/CodeFileTreeNode.py Tue Jun 05 15:29:58 2018 +0200
+++ b/CodeFileTreeNode.py Wed Jul 04 14:17:00 2018 +0200
@@ -139,11 +139,11 @@
def GetDataTypes(self, basetypes=False):
return self.GetCTRoot().GetDataTypes(basetypes=basetypes)
- def GenerateNewName(self, format, start_idx):
+ def GenerateNewName(self, name, format):
return self.GetCTRoot().GenerateNewName(
- None, None, format, start_idx,
- dict([(var.getname().upper(), True)
- for var in self.CodeFile.variables.getvariable()]))
+ None, name, format,
+ exclude=dict([(var.getname().upper(), True)
+ for var in self.CodeFile.variables.getvariable()]))
def SetVariables(self, variables):
self.CodeFile.variables.setvariable([])
--- a/ConfigTreeNode.py Tue Jun 05 15:29:58 2018 +0200
+++ b/ConfigTreeNode.py Wed Jul 04 14:17:00 2018 +0200
@@ -274,15 +274,14 @@
LocationCFilesAndCFLAGS = []
# confnode asks for some LDFLAGS
- if CTNLDFLAGS:
+ LDFLAGS = []
+ if CTNLDFLAGS is not None:
# LDFLAGS can be either string
- if isinstance(CTNLDFLAGS, str):
- LDFLAGS = [CTNLDFLAGS]
+ if isinstance(CTNLDFLAGS, str) or isinstance(CTNLDFLAGS, unicode):
+ LDFLAGS += [CTNLDFLAGS]
# or list of strings
elif isinstance(CTNLDFLAGS, list):
- LDFLAGS = CTNLDFLAGS[:]
- else:
- LDFLAGS = []
+ LDFLAGS += CTNLDFLAGS
# recurse through all children, and stack their results
for CTNChild in self.IECSortedChildren():
@@ -500,7 +499,10 @@
# Call the OnCloseMethod
CTNInstance.OnCTNClose()
# Delete confnode dir
- shutil.rmtree(CTNInstance.CTNPath())
+ try:
+ shutil.rmtree(CTNInstance.CTNPath())
+ except:
+ pass
# Remove child of Children
self.Children[CTNInstance.CTNType].remove(CTNInstance)
if len(self.Children[CTNInstance.CTNType]) == 0:
--- a/IDEFrame.py Tue Jun 05 15:29:58 2018 +0200
+++ b/IDEFrame.py Wed Jul 04 14:17:00 2018 +0200
@@ -1537,6 +1537,7 @@
self.ProjectTree.SetItemText(root, item_name)
self.ProjectTree.SetPyData(root, infos)
highlight_colours = self.Highlights.get(infos.get("tagname", None), (wx.Colour(255, 255, 255, 0), wx.BLACK))
+ self.ProjectTree.SetItemBackgroundColour(root, highlight_colours[0])
self.ProjectTree.SetItemTextColour(root, highlight_colours[1])
self.ProjectTree.SetItemExtraImage(root, None)
if infos["type"] == ITEM_POU:
--- a/PLCControler.py Tue Jun 05 15:29:58 2018 +0200
+++ b/PLCControler.py Wed Jul 04 14:17:00 2018 +0200
@@ -35,7 +35,6 @@
import util.paths as paths
from plcopen import *
from plcopen.types_enums import *
-from plcopen.XSLTModelQuery import _StringValue, _BoolValue, _translate_args
from plcopen.InstancesPathCollector import InstancesPathCollector
from plcopen.POUVariablesCollector import POUVariablesCollector
from plcopen.InstanceTagnameCollector import InstanceTagnameCollector
@@ -45,11 +44,10 @@
from PLCGenerator import *
duration_model = re.compile("(?:([0-9]{1,2})h)?(?:([0-9]{1,2})m(?!s))?(?:([0-9]{1,2})s)?(?:([0-9]{1,3}(?:\.[0-9]*)?)ms)?")
+VARIABLE_NAME_SUFFIX_MODEL = re.compile('(\d+)$')
ScriptDirectory = paths.AbsDir(__file__)
-
-
# Length of the buffer
UNDO_BUFFER_LENGTH = 20
@@ -1817,6 +1815,14 @@
return text
def GenerateNewName(self, tagname, name, format, start_idx=0, exclude=None, debug=False):
+ if name is not None:
+ result = re.search(VARIABLE_NAME_SUFFIX_MODEL, name)
+ if result is not None:
+ format = name[:result.start(1)] + '%d'
+ start_idx = int(result.group(1))
+ else:
+ format = name + '%d'
+
names = {} if exclude is None else exclude.copy()
if tagname is not None:
names.update(dict([(varname.upper(), True)
@@ -1832,6 +1838,14 @@
PLCOpenParser.GetElementClass("connector", "commonObjects"),
PLCOpenParser.GetElementClass("continuation", "commonObjects"))):
names[instance.getname().upper()] = True
+ elif words[0] == 'R':
+ element = self.GetEditedElement(tagname, debug)
+ for task in element.gettask():
+ names[task.getname().upper()] = True
+ for instance in task.getpouInstance():
+ names[instance.getname().upper()] = True
+ for instance in element.getpouInstance():
+ names[instance.getname().upper()] = True
else:
project = self.GetProject(debug)
if project is not None:
--- a/ProjectController.py Tue Jun 05 15:29:58 2018 +0200
+++ b/ProjectController.py Wed Jul 04 14:17:00 2018 +0200
@@ -37,7 +37,7 @@
import re
import tempfile
from types import ListType
-from threading import Timer, Lock, Thread
+from threading import Timer
from datetime import datetime
from weakref import WeakKeyDictionary
from itertools import izip
@@ -242,7 +242,6 @@
# Setup debug information
self.IECdebug_datas = {}
- self.IECdebug_lock = Lock()
self.DebugTimer = None
self.ResetIECProgramsAndVariables()
@@ -258,7 +257,6 @@
# After __init__ root confnode is not valid
self.ProjectPath = None
self._setBuildPath(None)
- self.DebugThread = None
self.debug_break = False
self.previous_plcstate = None
# copy ConfNodeMethods so that it can be later customized
@@ -1420,9 +1418,32 @@
self.UpdateMethodsFromPLCStatus()
def SnapshotAndResetDebugValuesBuffers(self):
+ if self._connector is not None:
+ plc_status, Traces = self._connector.GetTraceVariables()
+ # print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
+ if plc_status == "Started":
+ if len(Traces) > 0:
+ for debug_tick, debug_buff in Traces:
+ debug_vars = UnpackDebugBuffer(debug_buff, self.TracedIECTypes)
+ if debug_vars is not None and len(debug_vars) == len(self.TracedIECPath):
+ for IECPath, values_buffer, value in izip(
+ self.TracedIECPath,
+ self.DebugValuesBuffers,
+ debug_vars):
+ IECdebug_data = self.IECdebug_datas.get(IECPath, None)
+ if IECdebug_data is not None and value is not None:
+ forced = IECdebug_data[2:4] == ["Forced", value]
+ if not IECdebug_data[4] and len(values_buffer) > 0:
+ values_buffer[-1] = (value, forced)
+ else:
+ values_buffer.append((value, forced))
+ self.DebugTicks.append(debug_tick)
+
buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
[list() for dummy in xrange(len(self.TracedIECPath))])
+
ticks, self.DebugTicks = self.DebugTicks, []
+
return ticks, buffers
def RegisterDebugVarToConnector(self):
@@ -1431,7 +1452,6 @@
self.TracedIECPath = []
self.TracedIECTypes = []
if self._connector is not None:
- self.IECdebug_lock.acquire()
IECPathsToPop = []
for IECPath, data_tuple in self.IECdebug_datas.iteritems():
WeakCallableDict, _data_log, _status, fvalue, _buffer_list = data_tuple
@@ -1462,7 +1482,6 @@
self.TracedIECPath = []
self._connector.SetTraceVariablesList([])
self.SnapshotAndResetDebugValuesBuffers()
- self.IECdebug_lock.release()
def IsPLCStarted(self):
return self.previous_plcstate == "Started"
@@ -1494,7 +1513,6 @@
if IECPath != "__tick__" and IECPath not in self._IECPathToIdx:
return None
- self.IECdebug_lock.acquire()
# If no entry exist, create a new one with a fresh WeakKeyDictionary
IECdebug_data = self.IECdebug_datas.get(IECPath, None)
if IECdebug_data is None:
@@ -1510,14 +1528,11 @@
IECdebug_data[0][callableobj] = buffer_list
- self.IECdebug_lock.release()
-
self.ReArmDebugRegisterTimer()
return IECdebug_data[1]
def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
- self.IECdebug_lock.acquire()
IECdebug_data = self.IECdebug_datas.get(IECPath, None)
if IECdebug_data is not None:
IECdebug_data[0].pop(callableobj, None)
@@ -1528,14 +1543,11 @@
lambda x, y: x | y,
IECdebug_data[0].itervalues(),
False)
- self.IECdebug_lock.release()
self.ReArmDebugRegisterTimer()
def UnsubscribeAllDebugIECVariable(self):
- self.IECdebug_lock.acquire()
self.IECdebug_datas = {}
- self.IECdebug_lock.release()
self.ReArmDebugRegisterTimer()
@@ -1543,30 +1555,22 @@
if IECPath not in self.IECdebug_datas:
return
- self.IECdebug_lock.acquire()
-
# If no entry exist, create a new one with a fresh WeakKeyDictionary
IECdebug_data = self.IECdebug_datas.get(IECPath, None)
IECdebug_data[2] = "Forced"
IECdebug_data[3] = fvalue
- self.IECdebug_lock.release()
-
self.ReArmDebugRegisterTimer()
def ReleaseDebugIECVariable(self, IECPath):
if IECPath not in self.IECdebug_datas:
return
- self.IECdebug_lock.acquire()
-
# If no entry exist, create a new one with a fresh WeakKeyDictionary
IECdebug_data = self.IECdebug_datas.get(IECPath, None)
IECdebug_data[2] = "Registered"
IECdebug_data[3] = None
- self.IECdebug_lock.release()
-
self.ReArmDebugRegisterTimer()
def CallWeakcallables(self, IECPath, function_name, *cargs):
@@ -1590,51 +1594,8 @@
return -1, "No runtime connected!"
return self._connector.RemoteExec(script, **kwargs)
- def DebugThreadProc(self):
- """
- This thread waid PLC debug data, and dispatch them to subscribers
- """
- self.debug_break = False
- debug_getvar_retry = 0
- while (not self.debug_break) and (self._connector is not None):
- plc_status, Traces = self._connector.GetTraceVariables()
- debug_getvar_retry += 1
- # print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
- if plc_status == "Started":
- if len(Traces) > 0:
- self.IECdebug_lock.acquire()
- for debug_tick, debug_buff in Traces:
- debug_vars = UnpackDebugBuffer(debug_buff, self.TracedIECTypes)
- if debug_vars is not None and len(debug_vars) == len(self.TracedIECPath):
- for IECPath, values_buffer, value in izip(
- self.TracedIECPath,
- self.DebugValuesBuffers,
- debug_vars):
- IECdebug_data = self.IECdebug_datas.get(IECPath, None) # FIXME get
- if IECdebug_data is not None and value is not None:
- forced = IECdebug_data[2:4] == ["Forced", value]
- if not IECdebug_data[4] and len(values_buffer) > 0:
- values_buffer[-1] = (value, forced)
- else:
- values_buffer.append((value, forced))
- self.DebugTicks.append(debug_tick)
- debug_getvar_retry = 0
- self.IECdebug_lock.release()
-
- if debug_getvar_retry != 0:
- # Be patient, tollerate PLC to come with fresh samples
- time.sleep(0.1)
- else:
- self.debug_break = True
- self.logger.write(_("Debugger disabled\n"))
- self.DebugThread = None
- if self.DispatchDebugValuesTimer is not None:
- self.DispatchDebugValuesTimer.Stop()
-
def DispatchDebugValuesProc(self, event):
- self.IECdebug_lock.acquire()
debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers()
- self.IECdebug_lock.release()
start_time = time.time()
if len(self.TracedIECPath) == len(buffers):
for IECPath, values in izip(self.TracedIECPath, buffers):
@@ -1645,22 +1606,12 @@
delay = time.time() - start_time
next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay)
- if self.DispatchDebugValuesTimer is not None and self.DebugThread is not None:
+ if self.DispatchDebugValuesTimer is not None:
self.DispatchDebugValuesTimer.Start(
int(next_refresh * 1000), oneShot=True)
event.Skip()
def KillDebugThread(self):
- tmp_debugthread = self.DebugThread
- self.debug_break = True
- if tmp_debugthread is not None:
- self.logger.writeyield(_("Stopping debugger...\n"))
- tmp_debugthread.join(timeout=5)
- if tmp_debugthread.isAlive() and self.logger:
- self.logger.write_warning(_("Couldn't stop debugger.\n"))
- else:
- self.logger.write(_("Debugger stopped.\n"))
- self.DebugThread = None
if self.DispatchDebugValuesTimer is not None:
self.DispatchDebugValuesTimer.Stop()
@@ -1672,9 +1623,6 @@
if self.DispatchDebugValuesTimer is not None:
self.DispatchDebugValuesTimer.Start(
int(REFRESH_PERIOD * 1000), oneShot=True)
- if self.DebugThread is None:
- self.DebugThread = Thread(target=self.DebugThreadProc)
- self.DebugThread.start()
def _Run(self):
"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/BacnetSlaveEditor.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,1029 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard.
+# This files implements the bacnet plugin for Beremiz, adding BACnet server support.
+#
+# Copyright (C) 2017: Mario de Sousa (msousa@fe.up.pt)
+#
+# See COPYING file for copyrights details.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import wx
+from collections import Counter
+from pickle import dump
+from util.BitmapLibrary import GetBitmap
+
+
+
+# Import some libraries on Beremiz code...
+from controls.CustomGrid import CustomGrid
+from controls.CustomTable import CustomTable
+from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from graphics.GraphicCommons import ERROR_HIGHLIGHT
+
+
+
+# BACnet Engineering units taken from: ASHRAE 135-2016, clause/chapter 21
+BACnetEngineeringUnits = [
+ ('(Acceleration) meters-per-second-per-second (166)', 166 ),
+ ('(Area) square-meters (0)', 0 ),
+ ('(Area) square-centimeters (116)', 116 ),
+ ('(Area) square-feet (1)', 1 ),
+ ('(Area) square-inches (115)', 115 ),
+ ('(Currency) currency1 (105)', 105 ),
+ ('(Currency) currency2 (106)', 106 ),
+ ('(Currency) currency3 (107)', 107 ),
+ ('(Currency) currency4 (108)', 108 ),
+ ('(Currency) currency5 (109)', 109 ),
+ ('(Currency) currency6 (110)', 110 ),
+ ('(Currency) currency7 (111)', 111 ),
+ ('(Currency) currency8 (112)', 112 ),
+ ('(Currency) currency9 (113)', 113 ),
+ ('(Currency) currency10 (114)', 114 ),
+ ('(Electrical) milliamperes (2)', 2 ),
+ ('(Electrical) amperes (3)', 3 ),
+ ('(Electrical) amperes-per-meter (167)', 167 ),
+ ('(Electrical) amperes-per-square-meter (168)', 168 ),
+ ('(Electrical) ampere-square-meters (169)', 169 ),
+ ('(Electrical) decibels (199)', 199 ),
+ ('(Electrical) decibels-millivolt (200)', 200 ),
+ ('(Electrical) decibels-volt (201)', 201 ),
+ ('(Electrical) farads (170)', 170 ),
+ ('(Electrical) henrys (171)', 171 ),
+ ('(Electrical) ohms (4)', 4 ),
+ ('(Electrical) ohm-meter-squared-per-meter (237)', 237 ),
+ ('(Electrical) ohm-meters (172)', 172 ),
+ ('(Electrical) milliohms (145)', 145 ),
+ ('(Electrical) kilohms (122)', 122 ),
+ ('(Electrical) megohms (123)', 123 ),
+ ('(Electrical) microsiemens (190)', 190 ),
+ ('(Electrical) millisiemens (202)', 202 ),
+ ('(Electrical) siemens (173)', 173 ),
+ ('(Electrical) siemens-per-meter (174)', 174 ),
+ ('(Electrical) teslas (175)', 175 ),
+ ('(Electrical) volts (5)', 5 ),
+ ('(Electrical) millivolts (124)', 124 ),
+ ('(Electrical) kilovolts (6)', 6 ),
+ ('(Electrical) megavolts (7)', 7 ),
+ ('(Electrical) volt-amperes (8)', 8 ),
+ ('(Electrical) kilovolt-amperes (9)', 9 ),
+ ('(Electrical) megavolt-amperes (10)', 10 ),
+ ('(Electrical) volt-amperes-reactive (11)', 11 ),
+ ('(Electrical) kilovolt-amperes-reactive (12)', 12 ),
+ ('(Electrical) megavolt-amperes-reactive (13)', 13 ),
+ ('(Electrical) volts-per-degree-kelvin (176)', 176 ),
+ ('(Electrical) volts-per-meter (177)', 177 ),
+ ('(Electrical) degrees-phase (14)', 14 ),
+ ('(Electrical) power-factor (15)', 15 ),
+ ('(Electrical) webers (178)', 178 ),
+ ('(Energy) ampere-seconds (238)', 238 ),
+ ('(Energy) volt-ampere-hours (239)', 239 ),
+ ('(Energy) kilovolt-ampere-hours (240)', 240 ),
+ ('(Energy) megavolt-ampere-hours (241)', 241 ),
+ ('(Energy) volt-ampere-hours-reactive (242)', 242 ),
+ ('(Energy) kilovolt-ampere-hours-reactive (243)', 243 ),
+ ('(Energy) megavolt-ampere-hours-reactive (244)', 244 ),
+ ('(Energy) volt-square-hours (245)', 245 ),
+ ('(Energy) ampere-square-hours (246)', 246 ),
+ ('(Energy) joules (16)', 16 ),
+ ('(Energy) kilojoules (17)', 17 ),
+ ('(Energy) kilojoules-per-kilogram (125)', 125 ),
+ ('(Energy) megajoules (126)', 126 ),
+ ('(Energy) watt-hours (18)', 18 ),
+ ('(Energy) kilowatt-hours (19)', 19 ),
+ ('(Energy) megawatt-hours (146)', 146 ),
+ ('(Energy) watt-hours-reactive (203)', 203 ),
+ ('(Energy) kilowatt-hours-reactive (204)', 204 ),
+ ('(Energy) megawatt-hours-reactive (205)', 205 ),
+ ('(Energy) btus (20)', 20 ),
+ ('(Energy) kilo-btus (147)', 147 ),
+ ('(Energy) mega-btus (148)', 148 ),
+ ('(Energy) therms (21)', 21 ),
+ ('(Energy) ton-hours (22)', 22 ),
+ ('(Enthalpy) joules-per-kilogram-dry-air (23)', 23 ),
+ ('(Enthalpy) kilojoules-per-kilogram-dry-air (149)', 149 ),
+ ('(Enthalpy) megajoules-per-kilogram-dry-air (150)', 150 ),
+ ('(Enthalpy) btus-per-pound-dry-air (24)', 24 ),
+ ('(Enthalpy) btus-per-pound (117)', 117 ),
+ ('(Entropy) joules-per-degree-kelvin (127)', 127 ),
+ ('(Entropy) kilojoules-per-degree-kelvin (151)', 151 ),
+ ('(Entropy) megajoules-per-degree-kelvin (152)', 152 ),
+ ('(Entropy) joules-per-kilogram-degree-kelvin (128)', 128 ),
+ ('(Force) newton (153)', 153 ),
+ ('(Frequency) cycles-per-hour (25)', 25 ),
+ ('(Frequency) cycles-per-minute (26)', 26 ),
+ ('(Frequency) hertz (27)', 27 ),
+ ('(Frequency) kilohertz (129)', 129 ),
+ ('(Frequency) megahertz (130)', 130 ),
+ ('(Frequency) per-hour (131)', 131 ),
+ ('(Humidity) grams-of-water-per-kilogram-dry-air (28)', 28 ),
+ ('(Humidity) percent-relative-humidity (29)', 29 ),
+ ('(Length) micrometers (194)', 194 ),
+ ('(Length) millimeters (30)', 30 ),
+ ('(Length) centimeters (118)', 118 ),
+ ('(Length) kilometers (193)', 193 ),
+ ('(Length) meters (31)', 31 ),
+ ('(Length) inches (32)', 32 ),
+ ('(Length) feet (33)', 33 ),
+ ('(Light) candelas (179)', 179 ),
+ ('(Light) candelas-per-square-meter (180)', 180 ),
+ ('(Light) watts-per-square-foot (34)', 34 ),
+ ('(Light) watts-per-square-meter (35)', 35 ),
+ ('(Light) lumens (36)', 36 ),
+ ('(Light) luxes (37)', 37 ),
+ ('(Light) foot-candles (38)', 38 ),
+ ('(Mass) milligrams (196)', 196 ),
+ ('(Mass) grams (195)', 195 ),
+ ('(Mass) kilograms (39)', 39 ),
+ ('(Mass) pounds-mass (40)', 40 ),
+ ('(Mass) tons (41)', 41 ),
+ ('(Mass Flow) grams-per-second (154)', 154 ),
+ ('(Mass Flow) grams-per-minute (155)', 155 ),
+ ('(Mass Flow) kilograms-per-second (42)', 42 ),
+ ('(Mass Flow) kilograms-per-minute (43)', 43 ),
+ ('(Mass Flow) kilograms-per-hour (44)', 44 ),
+ ('(Mass Flow) pounds-mass-per-second (119)', 119 ),
+ ('(Mass Flow) pounds-mass-per-minute (45)', 45 ),
+ ('(Mass Flow) pounds-mass-per-hour (46)', 46 ),
+ ('(Mass Flow) tons-per-hour (156)', 156 ),
+ ('(Power) milliwatts (132)', 132 ),
+ ('(Power) watts (47)', 47 ),
+ ('(Power) kilowatts (48)', 48 ),
+ ('(Power) megawatts (49)', 49 ),
+ ('(Power) btus-per-hour (50)', 50 ),
+ ('(Power) kilo-btus-per-hour (157)', 157 ),
+ ('(Power) joule-per-hours (247)', 247 ),
+ ('(Power) horsepower (51)', 51 ),
+ ('(Power) tons-refrigeration (52)', 52 ),
+ ('(Pressure) pascals (53)', 53 ),
+ ('(Pressure) hectopascals (133)', 133 ),
+ ('(Pressure) kilopascals (54)', 54 ),
+ ('(Pressure) millibars (134)', 134 ),
+ ('(Pressure) bars (55)', 55 ),
+ ('(Pressure) pounds-force-per-square-inch (56)', 56 ),
+ ('(Pressure) millimeters-of-water (206)', 206 ),
+ ('(Pressure) centimeters-of-water (57)', 57 ),
+ ('(Pressure) inches-of-water (58)', 58 ),
+ ('(Pressure) millimeters-of-mercury (59)', 59 ),
+ ('(Pressure) centimeters-of-mercury (60)', 60 ),
+ ('(Pressure) inches-of-mercury (61)', 61 ),
+ ('(Temperature) degrees-celsius (62)', 62 ),
+ ('(Temperature) degrees-kelvin (63)', 63 ),
+ ('(Temperature) degrees-kelvin-per-hour (181)', 181 ),
+ ('(Temperature) degrees-kelvin-per-minute (182)', 182 ),
+ ('(Temperature) degrees-fahrenheit (64)', 64 ),
+ ('(Temperature) degree-days-celsius (65)', 65 ),
+ ('(Temperature) degree-days-fahrenheit (66)', 66 ),
+ ('(Temperature) delta-degrees-fahrenheit (120)', 120 ),
+ ('(Temperature) delta-degrees-kelvin (121)', 121 ),
+ ('(Time) years (67)', 67 ),
+ ('(Time) months (68)', 68 ),
+ ('(Time) weeks (69)', 69 ),
+ ('(Time) days (70)', 70 ),
+ ('(Time) hours (71)', 71 ),
+ ('(Time) minutes (72)', 72 ),
+ ('(Time) seconds (73)', 73 ),
+ ('(Time) hundredths-seconds (158)', 158 ),
+ ('(Time) milliseconds (159)', 159 ),
+ ('(Torque) newton-meters (160)', 160 ),
+ ('(Velocity) millimeters-per-second (161)', 161 ),
+ ('(Velocity) millimeters-per-minute (162)', 162 ),
+ ('(Velocity) meters-per-second (74)', 74 ),
+ ('(Velocity) meters-per-minute (163)', 163 ),
+ ('(Velocity) meters-per-hour (164)', 164 ),
+ ('(Velocity) kilometers-per-hour (75)', 75 ),
+ ('(Velocity) feet-per-second (76)', 76 ),
+ ('(Velocity) feet-per-minute (77)', 77 ),
+ ('(Velocity) miles-per-hour (78)', 78 ),
+ ('(Volume) cubic-feet (79)', 79 ),
+ ('(Volume) cubic-meters (80)', 80 ),
+ ('(Volume) imperial-gallons (81)', 81 ),
+ ('(Volume) milliliters (197)', 197 ),
+ ('(Volume) liters (82)', 82 ),
+ ('(Volume) us-gallons (83)', 83 ),
+ ('(Volumetric Flow) cubic-feet-per-second (142)', 142 ),
+ ('(Volumetric Flow) cubic-feet-per-minute (84)', 84 ),
+ ('(Volumetric Flow) million-standard-cubic-feet-per-minute (254)', 254 ),
+ ('(Volumetric Flow) cubic-feet-per-hour (191)', 191 ),
+ ('(Volumetric Flow) cubic-feet-per-day (248)', 248 ),
+ ('(Volumetric Flow) standard-cubic-feet-per-day (47808)', 47808 ),
+ ('(Volumetric Flow) million-standard-cubic-feet-per-day (47809)', 47809 ),
+ ('(Volumetric Flow) thousand-cubic-feet-per-day (47810)', 47810 ),
+ ('(Volumetric Flow) thousand-standard-cubic-feet-per-day (47811)', 47811 ),
+ ('(Volumetric Flow) pounds-mass-per-day (47812)', 47812 ),
+ ('(Volumetric Flow) cubic-meters-per-second (85)', 85 ),
+ ('(Volumetric Flow) cubic-meters-per-minute (165)', 165 ),
+ ('(Volumetric Flow) cubic-meters-per-hour (135)', 135 ),
+ ('(Volumetric Flow) cubic-meters-per-day (249)', 249 ),
+ ('(Volumetric Flow) imperial-gallons-per-minute (86)', 86 ),
+ ('(Volumetric Flow) milliliters-per-second (198)', 198 ),
+ ('(Volumetric Flow) liters-per-second (87)', 87 ),
+ ('(Volumetric Flow) liters-per-minute (88)', 88 ),
+ ('(Volumetric Flow) liters-per-hour (136)', 136 ),
+ ('(Volumetric Flow) us-gallons-per-minute (89)', 89 ),
+ ('(Volumetric Flow) us-gallons-per-hour (192)', 192 ),
+ ('(Other) degrees-angular (90)', 90 ),
+ ('(Other) degrees-celsius-per-hour (91)', 91 ),
+ ('(Other) degrees-celsius-per-minute (92)', 92 ),
+ ('(Other) degrees-fahrenheit-per-hour (93)', 93 ),
+ ('(Other) degrees-fahrenheit-per-minute (94)', 94 ),
+ ('(Other) joule-seconds (183)', 183 ),
+ ('(Other) kilograms-per-cubic-meter (186)', 186 ),
+ ('(Other) kilowatt-hours-per-square-meter (137)', 137 ),
+ ('(Other) kilowatt-hours-per-square-foot (138)', 138 ),
+ ('(Other) watt-hours-per-cubic-meter (250)', 250 ),
+ ('(Other) joules-per-cubic-meter (251)', 251 ),
+ ('(Other) megajoules-per-square-meter (139)', 139 ),
+ ('(Other) megajoules-per-square-foot (140)', 140 ),
+ ('(Other) mole-percent (252)', 252 ),
+ ('(Other) no-units (95)', 95 ),
+ ('(Other) newton-seconds (187)', 187 ),
+ ('(Other) newtons-per-meter (188)', 188 ),
+ ('(Other) parts-per-million (96)', 96 ),
+ ('(Other) parts-per-billion (97)', 97 ),
+ ('(Other) pascal-seconds (253)', 253 ),
+ ('(Other) percent (98)', 98 ),
+ ('(Other) percent-obscuration-per-foot (143)', 143 ),
+ ('(Other) percent-obscuration-per-meter (144)', 144 ),
+ ('(Other) percent-per-second (99)', 99 ),
+ ('(Other) per-minute (100)', 100 ),
+ ('(Other) per-second (101)', 101 ),
+ ('(Other) psi-per-degree-fahrenheit (102)', 102 ),
+ ('(Other) radians (103)', 103 ),
+ ('(Other) radians-per-second (184)', 184 ),
+ ('(Other) revolutions-per-minute (104)', 104 ),
+ ('(Other) square-meters-per-newton (185)', 185 ),
+ ('(Other) watts-per-meter-per-degree-kelvin (189)', 189 ),
+ ('(Other) watts-per-square-meter-degree-kelvin (141)', 141 ),
+ ('(Other) per-mille (207)', 207 ),
+ ('(Other) grams-per-gram (208)', 208 ),
+ ('(Other) kilograms-per-kilogram (209)', 209 ),
+ ('(Other) grams-per-kilogram (210)', 210 ),
+ ('(Other) milligrams-per-gram (211)', 211 ),
+ ('(Other) milligrams-per-kilogram (212)', 212 ),
+ ('(Other) grams-per-milliliter (213)', 213 ),
+ ('(Other) grams-per-liter (214)', 214 ),
+ ('(Other) milligrams-per-liter (215)', 215 ),
+ ('(Other) micrograms-per-liter (216)', 216 ),
+ ('(Other) grams-per-cubic-meter (217)', 217 ),
+ ('(Other) milligrams-per-cubic-meter (218)', 218 ),
+ ('(Other) micrograms-per-cubic-meter (219)', 219 ),
+ ('(Other) nanograms-per-cubic-meter (220)', 220 ),
+ ('(Other) grams-per-cubic-centimeter (221)', 221 ),
+ ('(Other) becquerels (222)', 222 ),
+ ('(Other) kilobecquerels (223)', 223 ),
+ ('(Other) megabecquerels (224)', 224 ),
+ ('(Other) gray (225)', 225 ),
+ ('(Other) milligray (226)', 226 ),
+ ('(Other) microgray (227)', 227 ),
+ ('(Other) sieverts (228)', 228 ),
+ ('(Other) millisieverts (229)', 229 ),
+ ('(Other) microsieverts (230)', 230 ),
+ ('(Other) microsieverts-per-hour (231)', 231 ),
+ ('(Other) millirems (47814)', 47814 ),
+ ('(Other) millirems-per-hour (47815)', 47815 ),
+ ('(Other) decibels-a (232)', 232 ),
+ ('(Other) nephelometric-turbidity-unit (233)', 233 ),
+ ('(Other) pH (234)', 234 ),
+ ('(Other) grams-per-square-meter (235)', 235 ),
+ ('(Other) minutes-per-degree-kelvin (236)', 236 )
+ ] # BACnetEngineeringUnits
+
+
+
+# ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 = 4194303
+# However, ObjectID 4194303 is not allowed!
+# 4194303 is used as a special value when object Id reference is referencing an undefined object
+# (similar to NULL in C)
+BACnetObjectID_MAX = 4194302
+BACnetObjectID_NUL = 4194303
+
+
+
+# A base class
+# what would be a purely virtual class in C++
+class ObjectProperties:
+ # this __init_() function is currently not beeing used!
+ def __init__(self):
+ #nothing to do
+ return
+
+
+class BinaryObject(ObjectProperties):
+ # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+ # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
+ # Be sure to use these exact names for these BACnet object properties!
+ PropertyNames = ["Object Identifier", "Object Name", "Description"]
+ ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+ ColumnSizes = [ 40 , 80 , 80 ]
+ PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor,
+ "GridCellRenderer" : wx.grid.GridCellNumberRenderer,
+ "GridCellEditorParam": "0,4194302"
+ # syntax for GridCellNumberEditor -> "min,max"
+ # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1
+ },
+ "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer": wx.grid.GridCellStringRenderer},
+ "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer": wx.grid.GridCellStringRenderer}
+ }
+
+class AnalogObject(ObjectProperties):
+ # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+ # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
+ # Be sure to use these exact names for these BACnet object properties!
+ #
+ # NOTE: Although it is not listed here (so it does not show up in the GUI, this object will also
+ # keep another entry for a virtual property named "Unit ID". This virtual property
+ # will store the ID corresponding to the "Engineering Units" currently chosen.
+ # This virtual property is kept synchronised to the "Engineering Units" property
+ # by the function PropertyChanged() which should be called by the OnCellChange event handler.
+ PropertyNames = ["Object Identifier", "Object Name", "Description", "Engineering Units"] #'Unit ID'
+ ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT ]
+ ColumnSizes = [ 40 , 80 , 80 , 200 ]
+ PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor,
+ "GridCellRenderer" : wx.grid.GridCellNumberRenderer,
+ "GridCellEditorParam": "0,4194302"
+ # syntax for GridCellNumberEditor -> "min,max"
+ # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1
+ },
+ "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer" : wx.grid.GridCellStringRenderer},
+ "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer" : wx.grid.GridCellStringRenderer},
+ "Engineering Units": {"GridCellEditor" : wx.grid.GridCellChoiceEditor,
+ "GridCellRenderer" : wx.grid.GridCellStringRenderer,
+ # use string renderer with choice editor!
+ "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits])
+ # syntax for GridCellChoiceEditor -> comma separated values
+ }
+ }
+
+ # obj_properties should be a dictionary, with keys "Object Identifier", "Object Name", "Description", ...
+ def UpdateVirtualProperties(self, obj_properties):
+ obj_properties["Unit ID"] = [x[1] for x in BACnetEngineeringUnits if x[0] == obj_properties["Engineering Units"]][0]
+
+
+
+class MultiSObject(ObjectProperties):
+ # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+ # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
+ # Be sure to use these exact names for these BACnet object properties!
+ PropertyNames = ["Object Identifier", "Object Name", "Description", "Number of States"]
+ ColumnAlignments = [ wx.ALIGN_RIGHT , wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_CENTER ]
+ ColumnSizes = [ 40 , 80 , 80 , 120 ]
+ PropertyConfig = {"Object Identifier": {"GridCellEditor" : wx.grid.GridCellNumberEditor,
+ "GridCellRenderer" : wx.grid.GridCellNumberRenderer,
+ "GridCellEditorParam": "0,4194302"
+ # syntax for GridCellNumberEditor -> "min,max"
+ # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1
+ },
+ "Object Name" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer" : wx.grid.GridCellStringRenderer},
+ "Description" : {"GridCellEditor" : wx.grid.GridCellTextEditor,
+ "GridCellRenderer" : wx.grid.GridCellStringRenderer},
+ "Number of States" : {"GridCellEditor" : wx.grid.GridCellNumberEditor,
+ "GridCellRenderer" : wx.grid.GridCellNumberRenderer,
+ "GridCellEditorParam": "1,255" # syntax for GridCellNumberEditor -> "min,max"
+ # MultiState Values are encoded in unsigned integer
+ # (in BACnet => uint8_t), and can not be 0.
+ # See ASHRAE 135-2016, section 12.20.4
+ }
+ }
+
+
+
+# The default values to use for each BACnet object type
+#
+# Note that the 'internal plugin parameters' get stored in the data table, but
+# are not visible in the GUI. They are used to generate the
+# EDE files as well as the C code
+class BVObject(BinaryObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Binary Value",
+ "Description" :"",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :5,
+ "Ctype" :"uint8_t",
+ "Settable" :"Y"
+ }
+
+class BOObject(BinaryObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Binary Output",
+ "Description" :"",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :4,
+ "Ctype" :"uint8_t",
+ "Settable" :"Y"
+ }
+
+class BIObject(BinaryObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Binary Input",
+ "Description" :"",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :3,
+ "Ctype" :"uint8_t",
+ "Settable" :"N"
+ }
+
+class AVObject(AnalogObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Analog Value",
+ "Description" :"",
+ "Engineering Units":'(Other) no-units (95)',
+ # internal plugin parameters...
+ "Unit ID" :95, # the ID of the engineering unit
+ # will get updated by UpdateVirtualProperties()
+ "BACnetObjTypeID" :2,
+ "Ctype" :"float",
+ "Settable" :"Y"
+ }
+
+class AOObject(AnalogObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Analog Output",
+ "Description" :"",
+ "Engineering Units":'(Other) no-units (95)',
+ # internal plugin parameters...
+ "Unit ID" :95, # the ID of the engineering unit
+ # will get updated by UpdateVirtualProperties()
+ "BACnetObjTypeID" :1,
+ "Ctype" :"float",
+ "Settable" :"Y"
+ }
+
+class AIObject(AnalogObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Analog Input",
+ "Description" :"",
+ "Engineering Units":'(Other) no-units (95)',
+ # internal plugin parameters...
+ "Unit ID" :95, # the ID of the engineering unit
+ # will get updated by UpdateVirtualProperties()
+ "BACnetObjTypeID" :0,
+ "Ctype" :"float",
+ "Settable" :"N"
+ }
+
+class MSVObject(MultiSObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Multi-state Value",
+ "Description" :"",
+ "Number of States" :"255",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :19,
+ "Ctype" :"uint8_t",
+ "Settable" :"Y"
+ }
+
+class MSOObject(MultiSObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Multi-state Output",
+ "Description" :"",
+ "Number of States" :"255",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :14,
+ "Ctype" :"uint8_t",
+ "Settable" :"Y"
+ }
+
+class MSIObject(MultiSObject):
+ DefaultValues = {"Object Identifier":"",
+ "Object Name" :"Multi-state Input",
+ "Description" :"",
+ "Number of States" :"255",
+ # internal plugin parameters...
+ "BACnetObjTypeID" :13,
+ "Ctype" :"uint8_t",
+ "Settable" :"N"
+ }
+
+
+
+
+
+
+
+
+
+
+class ObjectTable(CustomTable):
+ # A custom wx.grid.PyGridTableBase using user supplied data
+ #
+ # This will basically store a list of BACnet objects that the slave will support/implement.
+ # There will be one instance of this ObjectTable class for each BACnet object type
+ # (e.g. Binary Value, Analog Input, Multi State Output, ...)
+ #
+ # The list of BACnet objects will actually be stored within the self.data variable
+ # (declared in CustomTable). Self.data will be a list of dictionaries (one entry per BACnet
+ # object). All of these dictionaries in the self.data list will have entries whose keys actually
+ # depend on the BACnet type object being handled. The keys in the dictionary will be
+ # the entries in the PropertyNames list of one of the following classes:
+ # (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
+ #
+ # For example, when handling Binary Value BACnet objects,
+ # self.data will be a list of dictionaries (one entry per row)
+ # self.data[n] will be a dictionary, with keys "Object Identifier", "Object Name", "Description"
+ # for example: self.data[n] = {"Object Identifier":33, "Object Name":"room1", "Description":"xx"}
+ #
+ # Note that this ObjectTable class merely stores the configuration data.
+ # It does not control the display nor the editing of this data.
+ # This data is typically displayed within a grid, that is controlled by the ObjectGrid class.
+ #
+ def __init__(self, parent, data, BACnetObjectType):
+ # parent : the _BacnetSlavePlug object that is instantiating this ObjectTable
+ # data : a list with the data to be shown on the grid
+ # (i.e., a list containing the BACnet object properties)
+ # Instantiated in _BacnetSlavePlug
+ # BACnetObjectType: one of BinaryObject, AnalogObject, MultiSObject
+ # (or a class that derives from them).
+ # This is actually the class itself, and not a variable!!!
+ # However, self.BACnetObjectType will be an instance
+ # of said class as we later need to call methods from this class.
+ # (in particular, the UpdateVirtualProperties() method)
+ #
+ # The base class must be initialized *first*
+ CustomTable.__init__(self, parent, data, BACnetObjectType.PropertyNames)
+ self.BACnetObjectType = BACnetObjectType()
+ self.ChangesToSave = False
+
+ #def _GetRowEdit(self, row):
+ #row_edit = self.GetValueByName(row, "Edit")
+ #var_type = self.Parent.GetTagName()
+ #bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type)
+ #if bodytype in ["ST", "IL"]:
+ #row_edit = True;
+ #return row_edit
+
+ def _updateColAttrs(self, grid):
+ # wx.grid.Grid -> update the column attributes to add the
+ # appropriate renderer given the column name.
+ #
+ # Otherwise default to the default renderer.
+ #print "ObjectTable._updateColAttrs() called!!!"
+ for row in range(self.GetNumberRows()):
+ for col in range(self.GetNumberCols()):
+ PropertyName = self.BACnetObjectType.PropertyNames[col]
+ PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName]
+ grid.SetReadOnly (row, col, False)
+ grid.SetCellEditor (row, col, PropertyConfig["GridCellEditor"] ())
+ grid.SetCellRenderer (row, col, PropertyConfig["GridCellRenderer"]())
+ grid.SetCellBackgroundColour(row, col, wx.WHITE)
+ grid.SetCellTextColour (row, col, wx.BLACK)
+ if "GridCellEditorParam" in PropertyConfig:
+ grid.GetCellEditor(row, col).SetParameters(PropertyConfig["GridCellEditorParam"])
+ self.ResizeRow(grid, row)
+
+ def FindValueByName(self, PropertyName, PropertyValue):
+ # find the row whose property named PropertyName has the value PropertyValue
+ # Returns: row number
+ # for example, find the row where PropertyName "Object Identifier" has value 1002
+ # FindValueByName("Object Identifier", 1002).
+ for row in range(self.GetNumberRows()):
+ if int(self.GetValueByName(row, PropertyName)) == PropertyValue:
+ return row
+ return None
+
+ # Return a list containing all the values under the column named 'colname'
+ def GetAllValuesByName(self, colname):
+ values = []
+ for row in range(self.GetNumberRows()):
+ values.append(self.data[row].get(colname))
+ return values
+
+ # Returns a dictionary with:
+ # keys: IDs of BACnet objects
+ # value: number of BACnet objects using this same Id
+ # (values larger than 1 indicates an error as BACnet requires unique
+ # object IDs for objects of the same type)
+ def GetObjectIDCount(self):
+ # The dictionary is built by first creating a list containing the IDs of all BACnet objects
+ ObjectIDs = self.GetAllValuesByName("Object Identifier")
+ ObjectIDs_as_int = [int(x) for x in ObjectIDs] # list of integers instead of strings...
+ # This list is then transformed into a collections.Counter class
+ # Which is then transformed into a dictionary using dict()
+ return dict(Counter(ObjectIDs_as_int))
+
+ # Check whether any object ID is used more than once (not valid in BACnet)
+ # (returns True or False)
+ def HasDuplicateObjectIDs(self):
+ ObjectIDsCount = self.GetObjectIDCount()
+ for ObjName in ObjectIDsCount:
+ if ObjectIDsCount[ObjName] > 1:
+ return True
+ return False
+
+ # Update the virtual properties of the objects of the classes derived from ObjectProperties
+ # (currently only the AnalogObject class had virtua properties, i.e. a property
+ # that is determined/calculated based on the other properties)
+ def UpdateAllVirtualProperties(self):
+ if hasattr(self.BACnetObjectType, 'UpdateVirtualProperties'):
+ for ObjProp in self.data:
+ self.BACnetObjectType.UpdateVirtualProperties(ObjProp)
+
+
+
+class ObjectGrid(CustomGrid):
+ # A custom wx.grid.Grid (CustomGrid derives from wx.grid.Grid)
+ #
+ # Needed mostly to customise the initial values of newly added rows, and to
+ # validate if the inserted data follows BACnet rules.
+ #
+ #
+ # This ObjectGrid class:
+ # Creates and controls the GUI __grid__ for configuring all the BACnet objects of one
+ # (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...)
+ # This grid is currently displayed within one 'window' controlled by a ObjectEditor
+ # object (this organization is not likely to change in the future).
+ #
+ # The grid uses one line/row per BACnet object, and one column for each property of the BACnet
+ # object. The column titles change depending on the specific type of BACnet object being edited
+ # (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
+ # The editor to use for each column is also obtained from that class (e.g. TextEditor,
+ # NumberEditor, ...)
+ #
+ # This class does NOT store the data in the grid. It merely controls its display and editing.
+ # The data in the grid is stored within an object of class ObjectTable
+ #
+ def __init__(self, *args, **kwargs):
+ CustomGrid.__init__(self, *args, **kwargs)
+
+ # Called when a new row is added by clicking Add button
+ # call graph: CustomGrid.OnAddButton() --> CustomGrid.AddRow() --> ObjectGrid._AddRow()
+ def _AddRow(self, new_row):
+ if new_row > 0:
+ self.Table.InsertRow(new_row, self.Table.GetRow(new_row - 1).copy())
+ else:
+ self.Table.InsertRow(new_row, self.DefaultValue.copy())
+ self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL) # start off with invalid object ID
+ # Find an apropriate BACnet object ID for the new object.
+ # We choose a first attempt (based on object ID of previous line + 1)
+ new_object_id = 0
+ if new_row > 0:
+ new_object_id = int(self.Table.GetValueByName(new_row - 1, "Object Identifier"))
+ new_object_id += 1
+ # Check whether the chosen object ID is not already in use.
+ # If in use, add 1 to the attempted object ID and recheck...
+ while self.Table.FindValueByName("Object Identifier", new_object_id) is not None:
+ new_object_id += 1
+ # if reached end of object IDs, cycle back to 0
+ # (remember, we may have started at any inital object ID > 0, so it makes sense to cyclce back to 0)
+ # warning: We risk entering an inifinite loop if all object IDs are already used.
+ # The likelyhood of this happening is extremely low, (we would need 2^22 elements in the table!)
+ # so don't worry about it for now.
+ if new_object_id > BACnetObjectID_MAX:
+ new_object_id = 0
+ # Set the object ID of the new object to the apropriate value
+ # ... and append the ID to the default object name (so the name becomes unique)
+ new_object_name = self.DefaultValue.get("Object Name") + " " + str(new_object_id)
+ self.Table.SetValueByName(new_row, "Object Name" , new_object_name)
+ self.Table.SetValueByName(new_row, "Object Identifier", new_object_id)
+ self.Table.ResetView(self)
+ return new_row
+
+
+ # Called when a object ID is changed
+ # call graph: ObjectEditor.OnVariablesGridCellChange() --> this method
+ # Will check whether there is a duplicate object ID, and highlight it if so.
+ def HighlightDuplicateObjectIDs(self):
+ if self.Table.GetNumberRows() < 2:
+ # Less than 2 rows. No duplicates are possible!
+ return
+ IDsCount = self.Table.GetObjectIDCount()
+ # check ALL object IDs for duplicates...
+ for row in range(self.Table.GetNumberRows()):
+ obj_id1 = int(self.Table.GetValueByName(row, "Object Identifier"))
+ if IDsCount[obj_id1] > 1:
+ # More than 1 BACnet object using this ID! Let us Highlight this row with errors...
+ # TODO: change the hardcoded column number '0' to a number obtained at runtime
+ # that is guaranteed to match the column titled "Object Identifier"
+ self.SetCellBackgroundColour(row, 0, ERROR_HIGHLIGHT[0])
+ self.SetCellTextColour (row, 0, ERROR_HIGHLIGHT[1])
+ else:
+ self.SetCellBackgroundColour(row, 0, wx.WHITE)
+ self.SetCellTextColour (row, 0, wx.BLACK)
+ # Refresh the graphical display to take into account any changes we may have made
+ self.ForceRefresh()
+ return None
+
+
+ # Called when the user changes the name of BACnet object (using the GUI grid)
+ # call graph: ObjectEditor.OnVariablesGridCellChange() -->
+ # --> BacnetSlaveEditorPlug.HighlightAllDuplicateObjectNames() -->
+ # --> ObjectEditor.HighlightDuplicateObjectNames() -->
+ # --> (this method)
+ # Will check whether there is a duplicate BACnet object name, and highlight it if so.
+ #
+ # Since the names of BACnet objects must be unique within the whole bacnet server (and
+ # not just among the BACnet objects of the same class (e.g. Analog Value, Binary Input, ...)
+ # to work properly this method must be passed a list of the names of all BACnet objects
+ # currently configured.
+ #
+ # AllObjectNamesFreq: a dictionary using as key the names of all currently configured BACnet
+ # objects, and value the number of objects using this same name.
+ def HighlightDuplicateObjectNames(self, AllObjectNamesFreq):
+ for row in range(self.Table.GetNumberRows()):
+ # TODO: change the hardcoded column number '1' to a number obtained at runtime
+ # that is guaranteed to match the column titled "Object Name"
+ if AllObjectNamesFreq[self.Table.GetValueByName(row, "Object Name")] > 1:
+ # This is an error! Highlight it...
+ self.SetCellBackgroundColour(row, 1, ERROR_HIGHLIGHT[0])
+ self.SetCellTextColour (row, 1, ERROR_HIGHLIGHT[1])
+ else:
+ self.SetCellBackgroundColour(row, 1, wx.WHITE)
+ self.SetCellTextColour (row, 1, wx.BLACK)
+ # Refresh the graphical display to take into account any changes we may have made
+ self.ForceRefresh()
+ return None
+
+
+
+
+class ObjectEditor(wx.Panel):
+ # This ObjectEditor class:
+ # Creates and controls the GUI window for configuring all the BACnet objects of one
+ # (generic) BACnet object type (e.g. Binary Value, Analog Input, Multi State Output, ...)
+ # This 'window' is currenty displayed within one tab of the bacnet plugin, but this
+ # may change in the future!
+ #
+ # It includes a grid to display all the BACnet objects of its type , as well as the buttons
+ # to insert, delete and move (up/down) a BACnet object in the grid.
+ # It also includes the sizers and spacers required to lay out the grid and buttons
+ # in the wndow.
+ #
+ def __init__(self, parent, window, controller, ObjTable):
+ # window: the window in which the editor will open.
+ # controller: The ConfigTreeNode object that controlls the data presented by
+ # this 'config tree node editor'
+ #
+ # parent: wx._controls.Notebook
+ # window: BacnetSlaveEditorPlug (i.e. beremiz.bacnet.BacnetSlaveEditor.BacnetSlaveEditorPlug)
+ # controller: controller will be an object of class
+ # FinalCTNClass (i.e. beremiz.ConfigTreeNode.FinalCTNClass )
+ # (FinalCTNClass inherits from: ConfigTreeNode and _BacnetSlavePlug)
+ # (For the BACnet plugin, it is easier to think of controller as a _BacnetSlavePlug,
+ # as the other classes are generic to all plugins!!)
+ #
+ # ObjTable: The object of class ObjectTable that stores the data displayed in the grid.
+ # This object is instantiated and managed by the _BacnetSlavePlug class.
+ #
+ self.window = window
+ self.controller = controller
+ self.ObjTable = ObjTable
+
+ wx.Panel.__init__(self, parent)
+
+ # The main sizer, 2 rows: top row for buttons, bottom row for 2D grid
+ self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
+ self.MainSizer.AddGrowableCol(0)
+ self.MainSizer.AddGrowableRow(1)
+
+ # sizer placed on top row of main sizer:
+ # 1 row; 6 columns: 1 static text, one stretchable spacer, 4 buttons
+ controls_sizer = wx.FlexGridSizer(cols=6, hgap=4, rows=1, vgap=5)
+ controls_sizer.AddGrowableCol(0)
+ controls_sizer.AddGrowableRow(0)
+ self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
+
+ # the buttons that populate the controls sizer (itself in top row of the main sizer)
+ # NOTE: the _("string") function will translate the "string" to the local language
+ controls_sizer.Add(wx.StaticText(self, label=_('Object Properties:')), flag=wx.ALIGN_BOTTOM)
+ controls_sizer.AddStretchSpacer()
+ for name, bitmap, help in [
+ ("AddButton" , "add_element" , _("Add variable" )),
+ ("DeleteButton", "remove_element", _("Remove variable" )),
+ ("UpButton" , "up" , _("Move variable up" )),
+ ("DownButton" , "down" , _("Move variable down"))]:
+ button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
+ size=wx.Size(28, 28),
+ style=wx.NO_BORDER)
+ button.SetToolTipString(help)
+ setattr(self, name, button)
+ controls_sizer.Add(button)
+
+ # the variable grid that will populate the bottom row of the main sizer
+ panel = self
+ self.VariablesGrid = ObjectGrid(panel, style=wx.VSCROLL)
+ #self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) # use only to enable drag'n'drop
+ self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
+ #self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
+ #self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
+ self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
+
+ # Configure the Variables Grid...
+ self.VariablesGrid.SetRowLabelSize(0) # do not include a leftmost column containing the 'row label'
+ self.VariablesGrid.SetButtons({"Add": self.AddButton,
+ "Delete": self.DeleteButton,
+ "Up": self.UpButton,
+ "Down": self.DownButton})
+ # The custom grid needs to know the default values to use when 'AddButton' creates a new row
+ # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes
+ # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
+ # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
+ self.VariablesGrid.SetDefaultValue(self.ObjTable.BACnetObjectType.DefaultValues)
+
+ # self.ObjTable: The table that contains the data displayed in the grid
+ # This table was instantiated/created in the initializer for class _BacnetSlavePlug
+ self.VariablesGrid.SetTable(self.ObjTable)
+ self.VariablesGrid.SetEditable(True)
+ # set the column attributes (width, alignment)
+ # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes
+ # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
+ # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
+ ColumnAlignments = self.ObjTable.BACnetObjectType.ColumnAlignments
+ ColumnSizes = self.ObjTable.BACnetObjectType.ColumnSizes
+ for col in range( self.ObjTable.GetNumberCols()):
+ attr = wx.grid.GridCellAttr()
+ attr.SetAlignment(ColumnAlignments[col], wx.ALIGN_CENTRE)
+ self.VariablesGrid.SetColAttr(col, attr)
+ self.VariablesGrid.SetColMinimalWidth(col, ColumnSizes[col])
+ self.VariablesGrid.AutoSizeColumn(col, False)
+
+ # layout the items in all sizers, and show them too.
+ self.SetSizer(self.MainSizer) # Have the wondow 'own' the sizer...
+ #self.MainSizer.ShowItems(True) # not needed once the window 'owns' the sizer (SetSizer())
+ #self.MainSizer.Layout() # not needed once the window 'owns' the sizer (SetSizer())
+
+ # Refresh the view of the grid...
+ # We ask the table to do that, who in turn will configure the grid for us!!
+ # It will configure the CellRenderers and CellEditors taking into account the type of
+ # BACnet object being shown in the grid!!
+ #
+ # Yes, I (Mario de Sousa) know this architecture does not seem to make much sense.
+ # It seems to me that the cell renderers etc. should all be configured right here.
+ # Unfortunately we inherit from the customTable and customGrid classes in Beremiz
+ # (in order to maintain GUI consistency), so we have to adopt their way of doing things.
+ #
+ # NOTE: ObjectTable.ResetView() (remember, ObjTable is of class ObjectTable)
+ # calls ObjectTable._updateColAttrs(), who will do the configuring.
+ self.ObjTable.ResetView(self.VariablesGrid)
+
+
+ def RefreshView(self):
+ #print "ObjectEditor.RefreshView() called!!!"
+ # Check for Duplicate Object IDs is only done within same BACnet object type (ID is unique by type).
+ # The VariablesGrid class can handle it by itself.
+ self.VariablesGrid.HighlightDuplicateObjectIDs()
+ # Check for Duplicate Object Names must be done globally (Object Name is unique within bacnet server)
+ # Only the BacnetSlaveEditorPlug can and will handle this.
+ #self.window.HighlightAllDuplicateObjectNames()
+ pass
+
+ #########################################
+ # Event handlers for the Variables Grid #
+ #########################################
+ def OnVariablesGridCellChange(self, event):
+ row, col = event.GetRow(), event.GetCol()
+ #print "ObjectEditor.OnVariablesGridCellChange(row=%s, col=%s) called!!!" % (row, col)
+ self.ObjTable.ChangesToSave = True
+ if self.ObjTable.GetColLabelValue(col) == "Object Identifier":
+ # an Object ID was changed => must check duplicate object IDs.
+ self.VariablesGrid.HighlightDuplicateObjectIDs()
+ if self.ObjTable.GetColLabelValue(col) == "Object Name":
+ # an Object Name was changed => must check duplicate object names.
+ # Note that this must be done to _all_ BACnet objects, and not just the objects
+ # of the same BACnet class (Binary Value, Analog Input, ...)
+ # So we have the BacnetSlaveEditorPlug class do it...
+ self.window.HighlightAllDuplicateObjectNames()
+ # There are changes to save =>
+ # udate the enabled/disabled state of the 'save' option in the 'file' menu
+ self.window.RefreshBeremizWindow()
+ event.Skip()
+
+ def OnVariablesGridCellLeftClick(self, event):
+ row = event.GetRow()
+
+ def OnVariablesGridEditorShown(self, event):
+ row, col = event.GetRow(), event.GetCol()
+
+ def HighlightDuplicateObjectNames(self, AllObjectNamesFreq):
+ return self.VariablesGrid.HighlightDuplicateObjectNames(AllObjectNamesFreq)
+
+
+
+
+
+
+class BacnetSlaveEditorPlug(ConfTreeNodeEditor):
+ # inheritance tree
+ # wx.SplitterWindow-->EditorPanel-->ConfTreeNodeEditor-->BacnetSlaveEditorPlug
+ #
+ # self.Controller -> The object that controls the data displayed in this editor
+ # In our case, the object of class _BacnetSlavePlug
+
+ CONFNODEEDITOR_TABS = [
+ (_("Analog Value Objects"), "_create_AV_ObjectEditor"),
+ (_("Analog Output Objects"), "_create_AO_ObjectEditor"),
+ (_("Analog Input Objects"), "_create_AI_ObjectEditor"),
+ (_("Binary Value Objects"), "_create_BV_ObjectEditor"),
+ (_("Binary Output Objects"), "_create_BO_ObjectEditor"),
+ (_("Binary Input Objects"), "_create_BI_ObjectEditor"),
+ (_("Multi-State Value Objects"), "_create_MSV_ObjectEditor"),
+ (_("Multi-State Output Objects"), "_create_MSO_ObjectEditor"),
+ (_("Multi-State Input Objects"), "_create_MSI_ObjectEditor")]
+
+ def _create_AV_ObjectEditor(self, parent):
+ self.AV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AV_Obj"])
+ return self.AV_ObjectEditor
+
+ def _create_AO_ObjectEditor(self, parent):
+ self.AO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AO_Obj"])
+ return self.AO_ObjectEditor
+
+ def _create_AI_ObjectEditor(self, parent):
+ self.AI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AI_Obj"])
+ return self.AI_ObjectEditor
+
+ def _create_BV_ObjectEditor(self, parent):
+ self.BV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BV_Obj"])
+ return self.BV_ObjectEditor
+
+ def _create_BO_ObjectEditor(self, parent):
+ self.BO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BO_Obj"])
+ return self.BO_ObjectEditor
+
+ def _create_BI_ObjectEditor(self, parent):
+ self.BI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BI_Obj"])
+ return self.BI_ObjectEditor
+
+ def _create_MSV_ObjectEditor(self, parent):
+ self.MSV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSV_Obj"])
+ return self.MSV_ObjectEditor
+
+ def _create_MSO_ObjectEditor(self, parent):
+ self.MSO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSO_Obj"])
+ return self.MSO_ObjectEditor
+
+ def _create_MSI_ObjectEditor(self, parent):
+ self.MSI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSI_Obj"])
+ return self.MSI_ObjectEditor
+
+ def __init__(self, parent, controler, window, editable=True):
+ self.Editable = editable
+ ConfTreeNodeEditor.__init__(self, parent, controler, window)
+
+ def __del__(self):
+ self.Controler.OnCloseEditor(self)
+
+ def GetConfNodeMenuItems(self):
+ return []
+
+ def RefreshConfNodeMenu(self, confnode_menu):
+ return
+
+ def RefreshView(self):
+ self.HighlightAllDuplicateObjectNames()
+ ConfTreeNodeEditor.RefreshView(self)
+ self. AV_ObjectEditor.RefreshView()
+ self. AO_ObjectEditor.RefreshView()
+ self. AI_ObjectEditor.RefreshView()
+ self. BV_ObjectEditor.RefreshView()
+ self. BO_ObjectEditor.RefreshView()
+ self. BI_ObjectEditor.RefreshView()
+ self.MSV_ObjectEditor.RefreshView()
+ self.MSO_ObjectEditor.RefreshView()
+ self.MSI_ObjectEditor.RefreshView()
+
+ def HighlightAllDuplicateObjectNames(self):
+ ObjectNamesCount = self.Controler.GetObjectNamesCount()
+ self. AV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self. AO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self. AI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self. BV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self. BO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self. BI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self.MSV_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self.MSO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ self.MSI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
+ return None
+
+
+ def RefreshBeremizWindow(self):
+ # self.ParentWindow is the top level Beremiz class (object) that
+ # handles the beremiz window and layout
+ self.ParentWindow.RefreshTitle() # Refresh the title of the Beremiz window
+ # (it changes depending on whether there are
+ # changes to save!! )
+ self.ParentWindow.RefreshFileMenu() # Refresh the enabled/disabled state of the
+ # entries in the main 'file' menu.
+ # ('Save' sub-menu should become enabled
+ # if there are changes to save! )
+ ###self.ParentWindow.RefreshEditMenu()
+ ###self.ParentWindow.RefreshPageTitles()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This code is made available on the understanding that it will not be
+# used in safety-critical situations without a full and competent review.
+
+
+from bacnet import *
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/bacnet.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,697 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard.
+# This files implements the bacnet plugin for Beremiz, adding BACnet server support.
+#
+# Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This code is made available on the understanding that it will not be
+# used in safety-critical situations without a full and competent review.
+
+
+
+import os, sys
+from collections import Counter
+from datetime import datetime
+
+base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
+base_folder = os.path.join(base_folder, "..")
+BacnetPath = os.path.join(base_folder, "BACnet")
+BacnetLibraryPath = os.path.join(BacnetPath, "lib")
+BacnetIncludePath = os.path.join(BacnetPath, "include")
+BacnetIncludePortPath = os.path.join(BacnetPath, "ports")
+BacnetIncludePortPath = os.path.join(BacnetIncludePortPath, "linux")
+
+import wx
+import pickle
+
+from BacnetSlaveEditor import *
+from BacnetSlaveEditor import ObjectProperties
+from ConfigTreeNode import ConfigTreeNode
+from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
+
+# Parameters to be monkey patched in beremiz customizations
+BACNET_VENDOR_ID = 9999
+BACNET_VENDOR_NAME = "Beremiz.org"
+BACNET_DEVICE_MODEL_NAME = "Beremiz PLC"
+
+###################################################
+###################################################
+# #
+# S L A V E D E V I C E #
+# #
+###################################################
+###################################################
+
+# NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
+# The objects are instead instantiated from class FinalCTNClass
+# FinalCTNClass inherits from: - ConfigTreeNode
+# - The tree node plug (in our case _BacnetSlavePlug)
+#class _BacnetSlavePlug:
+class RootClass:
+ XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="BACnetServerNode">
+ <xsd:complexType>
+ <xsd:attribute name="Network_Interface" type="xsd:string" use="optional" default="eth0"/>
+ <xsd:attribute name="UDP_Port_Number" use="optional" default="47808">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="BACnet_Communication_Control_Password"
+ type="xsd:string" use="optional" default="Malba Tahan"/>
+ <xsd:attribute name="BACnet_Device_ID" use="optional" default="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="4194302"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="BACnet_Device_Name" type="xsd:string" use="optional" default="Beremiz device 0"/>
+ <xsd:attribute name="BACnet_Device_Location" type="xsd:string" use="optional" default=""/>
+ <xsd:attribute name="BACnet_Device_Description" type="xsd:string" use="optional" default="Beremiz device 0"/>
+ <xsd:attribute name="BACnet_Device_Application_Software_Version" type="xsd:string" use="optional" default="1.0"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
+ # so the Device instance ID is limited from 0 to 22^2-1 = 4194303
+ # However, 4194303 is reserved for special use (similar to NULL pointer), so last
+ # valid ID becomes 4194302
+
+
+ # The class/object that will render the graphical interface to edit the
+ # BacnetSlavePlug's configuration parameters. The object of class BacnetSlaveEditorPlug
+ # will be instantiated by the ConfigTreeNode class.
+ # This BacnetSlaveEditorPlug object can be accessed from _BacnetSlavePlug as
+ # 'self._View'
+ # See the following note to understand how this is possible!
+ #
+ # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
+ # The objects are instead instantiated from class FinalCTNClass
+ # FinalCTNClass inherits from: - ConfigTreeNode
+ # - The tree node plug (in our case _BacnetSlavePlug)
+ #
+ # This means that objects of class _BacnetSlavePlug may safely access all the members
+ # of classes ConfigTreeNode as well as FinalCTNClass (since they are always instantiated
+ # as a FinalCTNClass)
+ EditorType = BacnetSlaveEditorPlug
+
+ # The following classes follow the model/viewer design pattern
+ #
+ # _BacnetSlavePlug - contains the model (i.e. configuration parameters)
+ # BacnetSlaveEditorPlug - contains the viewer (and editor, so it includes the 'controller' part of the
+ # design pattern which in this case is not separated from the viewer)
+ #
+ # The _BacnetSlavePlug object is 'permanent', i.e. it exists as long as the beremiz project is open
+ # The BacnetSlaveEditorPlug object is 'transient', i.e. it exists only while the editor is visible/open
+ # in the editing panel. It is destoryed whenever
+ # the user closes the corresponding tab in the
+ # editing panel, and a new object is created when
+ # the editor is re-opened.
+ #
+ # _BacnetSlavePlug contains: AV_ObjTable, ...
+ # (these are the objects that actually store the config parameters or 'model'
+ # and are therefore stored to a file)
+ #
+ # _BacnetSlavePlug contains: AV_VarEditor, ...
+ # (these are the objects that implement a grid table to edit/view the
+ # corresponding mode parameters)
+ #
+ # Logic:
+ # - The xx_VarEditor classes inherit from wx.grid.Grid
+ # - The xx_ObjTable classes inherit from wx.grid.PyGridTableBase
+ # To be more precise, the inheritance tree is actually:
+ # xx_VarEditor -> ObjectGrid -> CustomGrid -> wx.grid.Grid
+ # xx_ObjTable -> ObjectTable -> CustomTable -> wx.grid.PyGridTableBase)
+ #
+ # Note that wx.grid.Grid is prepared to work with wx.grid.PyGridTableBase as the container of
+ # data that is displayed and edited in the Grid.
+
+
+ ConfNodeMethods = [
+ {"bitmap" : "ExportSlave",
+ "name" : _("Export slave"),
+ "tooltip" : _("Export BACnet slave to EDE file"),
+ "method" : "_ExportBacnetSlave"},
+ ]
+
+ def __init__(self):
+ # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables
+ # in this BACnet server.
+ self.ObjTablesData = {}
+ self.ObjTablesData[ "AV_Obj"] = [] # Each list will contain an entry for each row in the xxxxVar grid!!
+ self.ObjTablesData[ "AO_Obj"] = [] # Each entry/row will be a dictionary
+ self.ObjTablesData[ "AI_Obj"] = [] # Each dictionary will contain all entries/data
+ self.ObjTablesData[ "BV_Obj"] = [] # for one row in the grid.
+ self.ObjTablesData[ "BO_Obj"] = [] # Same structure as explained above...
+ self.ObjTablesData[ "BI_Obj"] = [] # Same structure as explained above...
+ self.ObjTablesData["MSV_Obj"] = [] # Same structure as explained above...
+ self.ObjTablesData["MSO_Obj"] = [] # Same structure as explained above...
+ self.ObjTablesData["MSI_Obj"] = [] # Same structure as explained above...
+
+ self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version":1}
+ # EDE files inlcude extra parameters (ex. file version)
+ # We would like to save the parameters the user configures
+ # so they are available the next time the user opens the project.
+ # Since this plugin is only storing the ObjTablesData[] dict
+ # to file, we add that info to this dictionary too.
+ # Yes, I know this is kind of a hack.
+
+ filepath = self.GetFileName()
+ if(os.path.isfile(filepath)):
+ self.LoadFromFile(filepath)
+
+ self.ObjTables = {}
+ self.ObjTables[ "AV_Obj"] = ObjectTable(self, self.ObjTablesData[ "AV_Obj"], AVObject)
+ self.ObjTables[ "AO_Obj"] = ObjectTable(self, self.ObjTablesData[ "AO_Obj"], AOObject)
+ self.ObjTables[ "AI_Obj"] = ObjectTable(self, self.ObjTablesData[ "AI_Obj"], AIObject)
+ self.ObjTables[ "BV_Obj"] = ObjectTable(self, self.ObjTablesData[ "BV_Obj"], BVObject)
+ self.ObjTables[ "BO_Obj"] = ObjectTable(self, self.ObjTablesData[ "BO_Obj"], BOObject)
+ self.ObjTables[ "BI_Obj"] = ObjectTable(self, self.ObjTablesData[ "BI_Obj"], BIObject)
+ self.ObjTables["MSV_Obj"] = ObjectTable(self, self.ObjTablesData["MSV_Obj"], MSVObject)
+ self.ObjTables["MSO_Obj"] = ObjectTable(self, self.ObjTablesData["MSO_Obj"], MSOObject)
+ self.ObjTables["MSI_Obj"] = ObjectTable(self, self.ObjTablesData["MSI_Obj"], MSIObject)
+ # list containing the data in the table <--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+ ######################################
+ # Functions to be called by CTNClass #
+ ######################################
+ # The following functions would be somewhat equvalent to virtual functions/methods in C++ classes
+ # They will be called by the base class (CTNClass) from which this _BacnetSlavePlug class derives.
+
+ def GetCurrentNodeName(self):
+ return self.CTNName()
+
+ def GetFileName(self):
+ return os.path.join(self.CTNPath(), 'bacnet_slave')
+
+ def OnCTNSave(self, from_project_path=None):
+ return self.SaveToFile(self.GetFileName())
+
+
+ def CTNTestModified(self):
+ # self.ChangesToSave: Check whether any of the parameters, defined in the XSD above, were changed.
+ # This is handled by the ConfigTreeNode class
+ # (Remember that no objects are ever instantiated from _BacnetSlavePlug.
+ # Objects are instead created from FinalCTNClass, which derives from
+ # _BacnetSlavePlug and ConfigTreeNode. This means that we can exceptionally
+ # consider that all objects of type _BacnetSlavePlug will also be a ConfigTreeNode).
+ result = self.ChangesToSave or self.ObjTables[ "AV_Obj"].ChangesToSave \
+ or self.ObjTables[ "AO_Obj"].ChangesToSave \
+ or self.ObjTables[ "AI_Obj"].ChangesToSave \
+ or self.ObjTables[ "BV_Obj"].ChangesToSave \
+ or self.ObjTables[ "BO_Obj"].ChangesToSave \
+ or self.ObjTables[ "BI_Obj"].ChangesToSave \
+ or self.ObjTables["MSV_Obj"].ChangesToSave \
+ or self.ObjTables["MSO_Obj"].ChangesToSave \
+ or self.ObjTables["MSI_Obj"].ChangesToSave
+ return result
+
+ ### Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
+ ##def _OpenView(self, name=None, onlyopened=False):
+ ##print "_BacnetSlavePlug._OpenView() Called!!!"
+ ##ConfigTreeNode._OpenView(self, name, onlyopened)
+ ###print self._View
+ #####if self._View is not None:
+ #####self._View.SetBusId(self.GetCurrentLocation())
+ ##return self._View
+
+
+ def GetVariableLocationTree(self):
+ current_location = self.GetCurrentLocation()
+ # see comment in CTNGenerate_C regarding identical line of code!
+ locstr = ".".join(map(str,current_location))
+
+ # IDs used by BACnet to identify object types/class.
+ # OBJECT_ANALOG_INPUT = 0,
+ # OBJECT_ANALOG_OUTPUT = 1,
+ # OBJECT_ANALOG_VALUE = 2,
+ # OBJECT_BINARY_INPUT = 3,
+ # OBJECT_BINARY_OUTPUT = 4,
+ # OBJECT_BINARY_VALUE = 5,
+ # OBJECT_MULTI_STATE_INPUT = 13,
+ # OBJECT_MULTI_STATE_OUTPUT = 14,
+ # OBJECT_MULTI_STATE_VALUE = 19,
+ #
+ # Since Binary Value, Analog Value, etc. objects may use the same
+ # object ID (since they have distinct class IDs), we must also distinguish them in some way in
+ # the %MX0.3.4 IEC 61131-3 syntax.
+ #
+ # For this reason we add the BACnet class identifier to the %MX0.5.3 location.
+ # For example, for a BACnet plugin in location '0' of the Beremiz configuration tree,
+ # all Binary Values will be mapped onto: %MX0.5.xxx (xxx is object ID)
+ # all Multi State Values will be mapped onto: %MB0.19.xxx (xxx is object ID)
+ # all Analog Values will be mapped onto: %MD0.2.xxx (xxx is object ID)
+ # etc..
+ #
+ # Value objects will be mapped onto %M
+ # Input objects will be mapped onto %I
+ # Output objects will be mapped onto %Q
+
+ BACnetEntries = []
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "AV_Obj"], 32, 'REAL', 'D', locstr+ '.2', 'Analog Values'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "AO_Obj"], 32, 'REAL', 'D', locstr+ '.1', 'Analog Outputs'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "AI_Obj"], 32, 'REAL', 'D', locstr+ '.0', 'Analog Inputs'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "BV_Obj"], 1, 'BOOL', 'X', locstr+ '.5', 'Binary Values'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "BO_Obj"], 1, 'BOOL', 'X', locstr+ '.4', 'Binary Outputs'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData[ "BI_Obj"], 1, 'BOOL', 'X', locstr+ '.3', 'Binary Inputs'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData["MSV_Obj"], 8, 'BYTE', 'B', locstr+'.19', 'Multi State Values'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData["MSO_Obj"], 8, 'BYTE', 'B', locstr+'.14', 'Multi State Outputs'))
+ BACnetEntries.append(self.GetSlaveLocationTree(
+ self.ObjTablesData["MSI_Obj"], 8, 'BYTE', 'B', locstr+'.13', 'Multi State Inputs'))
+
+ return {"name": self.BaseParams.getName(),
+ "type": LOCATION_CONFNODE,
+ "location": locstr + ".x",
+ "children": BACnetEntries}
+
+
+ ############################
+ # Helper functions/methods #
+ ############################
+ # a helper function to GetVariableLocationTree()
+ def GetSlaveLocationTree(self, ObjTablesData, size_in_bits, IECdatatype, location_size, location_str, name):
+ BACnetObjectEntries = []
+ for xx_ObjProp in ObjTablesData:
+ BACnetObjectEntries.append({
+ "name": str(xx_ObjProp["Object Identifier"]) + ': ' + xx_ObjProp["Object Name"],
+ "type": LOCATION_VAR_MEMORY, # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
+ "size": size_in_bits, # 1 or 16
+ "IEC_type": IECdatatype, # 'BOOL', 'WORD', ...
+ "var_name": "var_name", # seems to be ignored??
+ "location": location_size + location_str + "." + str(xx_ObjProp["Object Identifier"]),
+ "description": "description", # seems to be ignored?
+ "children": []})
+
+ BACnetEntries = []
+ return {"name": name,
+ "type": LOCATION_CONFNODE,
+ "location": location_str + ".x",
+ "children": BACnetObjectEntries}
+
+
+ # Returns a dictionary with:
+ # keys: names of BACnet objects
+ # value: number of BACnet objects using this same name
+ # (values larger than 1 indicates an error as BACnet requires unique names)
+ def GetObjectNamesCount(self):
+ # The dictionary is built by first creating a list containing the names of _ALL_
+ # BACnet objects currently configured by the user (using the GUI)
+ ObjectNames = []
+ ObjectNames.extend(self.ObjTables[ "AV_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables[ "AO_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables[ "AI_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables[ "BV_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables[ "BO_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables[ "BI_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
+ ObjectNames.extend(self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
+ # This list is then transformed into a collections.Counter class
+ # Which is then transformed into a dictionary using dict()
+ return dict(Counter(ObjectNames))
+
+ # Check whether the current configuration contains BACnet objects configured
+ # with the same identical object name (returns True or False)
+ def HasDuplicateObjectNames(self):
+ ObjectNamesCount = self.GetObjectNamesCount()
+ for ObjName in ObjectNamesCount:
+ if ObjectNamesCount[ObjName] > 1:
+ return True
+ return False
+
+ # Check whether any object ID is used more than once (not valid in BACnet)
+ # (returns True or False)
+ def HasDuplicateObjectIDs(self):
+ res = self.ObjTables[ "AV_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables[ "AO_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables[ "AI_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables[ "BV_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables[ "BO_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables[ "BI_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables["MSV_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables["MSO_Obj"].HasDuplicateObjectIDs()
+ res = res or self.ObjTables["MSI_Obj"].HasDuplicateObjectIDs()
+ return res
+
+
+ #######################################################
+ # Methods related to files (saving/loading/exporting) #
+ #######################################################
+ def SaveToFile(self, filepath):
+ # Save node data in file
+ # The configuration data declared in the XSD string will be saved by the ConfigTreeNode class,
+ # so we only need to save the data that is stored in ObjTablesData objects
+ # Note that we do not store the ObjTables objects. ObjTables is of a class that
+ # contains more stuff we do not need to store. Actually it is a bad idea to store
+ # this extra stuff (as we would make the files we generate dependent on the actual
+ # version of the wx library we are using!!! Remember that ObjTables evetually
+ # derives/inherits from wx.grid.PyGridTableBase). Another reason not to store the whole
+ # object is because it is not pickable (i.e. pickle.dump() cannot handle it)!!
+ try:
+ fd = open(filepath, "w")
+ pickle.dump(self.ObjTablesData, fd)
+ fd.close()
+ # On successfull save, reset flags to indicate no more changes that need saving
+ self.ObjTables[ "AV_Obj"].ChangesToSave = False
+ self.ObjTables[ "AO_Obj"].ChangesToSave = False
+ self.ObjTables[ "AI_Obj"].ChangesToSave = False
+ self.ObjTables[ "BV_Obj"].ChangesToSave = False
+ self.ObjTables[ "BO_Obj"].ChangesToSave = False
+ self.ObjTables[ "BI_Obj"].ChangesToSave = False
+ self.ObjTables["MSV_Obj"].ChangesToSave = False
+ self.ObjTables["MSO_Obj"].ChangesToSave = False
+ self.ObjTables["MSI_Obj"].ChangesToSave = False
+ return True
+ except:
+ return _("Unable to save to file \"%s\"!")%filepath
+
+ def LoadFromFile(self, filepath):
+ # Load the data that is saved in SaveToFile()
+ try:
+ fd = open(filepath, "r")
+ self.ObjTablesData = pickle.load(fd)
+ fd.close()
+ return True
+ except:
+ return _("Unable to load file \"%s\"!")%filepath
+
+ def _ExportBacnetSlave(self):
+ dialog = wx.FileDialog(self.GetCTRoot().AppFrame,
+ _("Choose a file"),
+ os.path.expanduser("~"),
+ "%s_EDE.csv" % self.CTNName(),
+ _("EDE files (*_EDE.csv)|*_EDE.csv|All files|*.*"),
+ wx.SAVE|wx.OVERWRITE_PROMPT)
+ if dialog.ShowModal() == wx.ID_OK:
+ result = self.GenerateEDEFile(dialog.GetPath())
+ result = False
+ if result:
+ self.GetCTRoot().logger.write_error(_("Error: Export slave failed\n"))
+ dialog.Destroy()
+
+
+ def GenerateEDEFile(self, filename):
+ template_file_dir = os.path.join(os.path.split(__file__)[0],"ede_files")
+
+ #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+ # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
+ BACnet_Device_ID = self.BACnetServerNode.getBACnet_Device_ID()
+
+ # The EDE file contains a header that includes general project data (name, author, ...)
+ # Instead of asking the user for this data, we get it from the configuration
+ # of the Beremiz project itself.
+ # We ask the root Config Tree Node for the data...
+ ProjProp = {}
+ FileProp = {}
+ CTN_Root = self.GetCTRoot() # this should be an object of class ProjectController
+ Project = CTN_Root.Project # this should be an object capable of parsing
+ # PLCopen XML files. The parser is created automatically
+ # (i.e. using GenerateParserFromXSD() from xmlclass module)
+ # using the PLCopen XSD file defining the format of the XML.
+ # See the file plcopen/plcopen.py
+ if Project is not None:
+ # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
+ # plcopn/plcopen.py We cannot rely on their existance
+ if getattr(Project, "getcontentHeader", None) is not None:
+ ProjProp = Project.getcontentHeader()
+ # getcontentHeader() returns a dictionary. Available keys are:
+ # "projectName", "projectVersion", "modificationDateTime",
+ # "organization", "authorName", "language", "pageSize", "scaling"
+ if getattr(Project, "getfileHeader", None) is not None:
+ FileProp = Project.getfileHeader()
+ # getfileHeader() returns a dictionary. Available keys are:
+ # "companyName", "companyURL", "productName", "productVersion",
+ # "productRelease", "creationDateTime", "contentDescription"
+
+ ProjName = ""
+ if "projectName" in ProjProp:
+ ProjName = ProjProp["projectName"]
+ ProjAuthor = ""
+ if "companyName" in FileProp:
+ ProjAuthor += "(" + FileProp["companyName"] + ")"
+ if "authorName" in ProjProp:
+ ProjAuthor = ProjProp["authorName"] + " " + ProjAuthor
+
+ projdata_dict = {}
+ projdata_dict["Project Name"] = ProjName
+ projdata_dict["Project Author"] = ProjAuthor
+ projdata_dict["Current Time"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ projdata_dict["EDE file version"] = self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"]
+
+ # Next time we generate an EDE file, use another version!
+ self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"] += 1
+
+ AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
+
+ BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
+
+ MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
+
+ Objects_List = []
+ for ObjType, params_format in [
+ ("AV" , AX_params_format ), ("AO" , AX_params_format ), ("AI" , AX_params_format ),
+ ("BV" , BX_params_format ), ("BO" , BX_params_format ), ("BI" , BX_params_format ),
+ ("MSV", MSX_params_format ), ("MSO", MSX_params_format ), ("MSI", MSX_params_format )
+ ]:
+ self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
+ for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
+ Objects_List.append(params_format % ObjProp)
+
+ # Normalize filename
+ for extension in ["_EDE.csv", "_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
+ if filename.lower().endswith(extension.lower()):
+ filename = filename[:-len(extension)]
+
+ # EDE_header
+ generate_file_name = filename + "_EDE.csv"
+ template_file_name = os.path.join(template_file_dir,"template_EDE.csv")
+ generate_file_content = open(template_file_name).read() % projdata_dict
+ generate_file_handle = open(generate_file_name,'w')
+ generate_file_handle .write(generate_file_content)
+ generate_file_handle .write("\n".join(Objects_List))
+ generate_file_handle .close()
+
+ # templates of remaining files do not need changes. They are simply copied unchanged!
+ for extension in ["_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
+ generate_file_name = filename + extension
+ template_file_name = os.path.join(template_file_dir,"template" + extension)
+ generate_file_content = open(template_file_name).read()
+ generate_file_handle = open(generate_file_name,'w')
+ generate_file_handle .write(generate_file_content)
+ generate_file_handle .close()
+
+
+ #############################
+ # Generate the source files #
+ #############################
+ def CTNGenerate_C(self, buildpath, locations):
+ # Determine the current location in Beremiz's project configuration tree
+ current_location = self.GetCurrentLocation()
+ # The current location of this plugin in Beremiz's configuration tree, separated by underscores
+ # NOTE: Since BACnet plugin currently does not use sub-branches in the tree (in other words, this
+ # _BacnetSlavePlug class was actually renamed as the RootClass), the current_location_dots
+ # will actually be a single number (e.g.: 0 or 3 or 6, corresponding to the location
+ # in which the plugin was inserted in the Beremiz configuration tree on Beremiz's left panel).
+ locstr = "_".join(map(str,current_location))
+
+ # First check whether all the current parameters (inserted by user in the GUI) are valid...
+ if self.HasDuplicateObjectNames():
+ self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object names.\n")%(locstr, self.CTNName()))
+ raise Exception, False
+ # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
+
+ if self.HasDuplicateObjectIDs():
+ self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object identifiers.\n")%(locstr, self.CTNName()))
+ raise Exception, False
+ # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
+
+ #-------------------------------------------------------------------------------
+ # Create and populate the loc_dict dictionary with all parameters needed to configure
+ # the generated source code (.c and .h files)
+ #-------------------------------------------------------------------------------
+
+ # 1) Create the dictionary (loc_dict = {})
+ loc_dict = {}
+ loc_dict["locstr"] = locstr
+
+ #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+ # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
+ loc_dict["network_interface"] = self.BACnetServerNode.getNetwork_Interface()
+ loc_dict["port_number"] = self.BACnetServerNode.getUDP_Port_Number()
+ loc_dict["BACnet_Device_ID"] = self.BACnetServerNode.getBACnet_Device_ID()
+ loc_dict["BACnet_Device_Name"] = self.BACnetServerNode.getBACnet_Device_Name()
+ loc_dict["BACnet_Comm_Control_Password"] = self.BACnetServerNode.getBACnet_Communication_Control_Password()
+ loc_dict["BACnet_Device_Location"] = self.BACnetServerNode.getBACnet_Device_Location()
+ loc_dict["BACnet_Device_Description"] = self.BACnetServerNode.getBACnet_Device_Description()
+ loc_dict["BACnet_Device_AppSoft_Version"]= self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
+ loc_dict["BACnet_Vendor_ID"] = BACNET_VENDOR_ID
+ loc_dict["BACnet_Vendor_Name"] = BACNET_VENDOR_NAME
+ loc_dict["BACnet_Model_Name"] = BACNET_DEVICE_MODEL_NAME
+
+ # 2) Add the data specific to each BACnet object type
+ # For each BACnet object type, start off by creating some intermediate helpful lists
+ # a) parameters_list containing the strings that will
+ # be included in the C source code, and which will initialize the struct with the
+ # object (Analog Value, Binary Value, or Multi State Value) parameters
+ # b) locatedvar_list containing the strings that will
+ # declare the memory to store the located variables, as well as the
+ # pointers (required by matiec) that point to that memory.
+
+ # format for delaring IEC 61131-3 variable (and pointer) onto which BACnet object is mapped
+ locvar_format = '%(Ctype)s ___%(loc)s_%(Object Identifier)s; ' + \
+ '%(Ctype)s *__%(loc)s_%(Object Identifier)s = &___%(loc)s_%(Object Identifier)s;'
+
+ # format for initializing a ANALOG_VALUE_DESCR struct in C code
+ # also valid for ANALOG_INPUT and ANALOG_OUTPUT
+ AX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+ '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
+ # format for initializing a BINARY_VALUE_DESCR struct in C code
+ # also valid for BINARY_INPUT and BINARY_OUTPUT
+ BX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+ '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
+
+ # format for initializing a MULTISTATE_VALUE_DESCR struct in C code
+ # also valid for MULTISTATE_INPUT and MULTISTATE_OUTPUT
+ MSX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
+ '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
+
+ AV_locstr = 'MD' + locstr+'_2' # see the comment in GetVariableLocationTree() to grok the '_2'
+ AO_locstr = 'QD' + locstr+'_1' # see the comment in GetVariableLocationTree() to grok the '_1'
+ AI_locstr = 'ID' + locstr+'_0' # see the comment in GetVariableLocationTree() to grok the '_0'
+ BV_locstr = 'MX' + locstr+'_5' # see the comment in GetVariableLocationTree() to grok the '_5'
+ BO_locstr = 'QX' + locstr+'_4' # see the comment in GetVariableLocationTree() to grok the '_4'
+ BI_locstr = 'IX' + locstr+'_3' # see the comment in GetVariableLocationTree() to grok the '_3'
+ MSV_locstr = 'MB' + locstr+'_19' # see the comment in GetVariableLocationTree() to grok the '_19'
+ MSO_locstr = 'QB' + locstr+'_14' # see the comment in GetVariableLocationTree() to grok the '_14'
+ MSI_locstr = 'IB' + locstr+'_13' # see the comment in GetVariableLocationTree() to grok the '_13'
+
+
+ for ObjType, ObjLocStr, params_format in [
+ ("AV" , AV_locstr, AX_params_format ),
+ ("AO" , AO_locstr, AX_params_format ),
+ ("AI" , AI_locstr, AX_params_format ),
+ ("BV" , BV_locstr, BX_params_format ),
+ ("BO" , BO_locstr, BX_params_format ),
+ ("BI" , BI_locstr, BX_params_format ),
+ ("MSV" , MSV_locstr, MSX_params_format ),
+ ("MSO" , MSO_locstr, MSX_params_format ),
+ ("MSI" , MSI_locstr, MSX_params_format )
+ ]:
+ parameters_list = []
+ locatedvar_list = []
+ self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
+ for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
+ ObjProp["loc" ] = ObjLocStr
+ parameters_list.append(params_format % ObjProp)
+ locatedvar_list.append(locvar_format % ObjProp)
+ loc_dict[ ObjType + "_count"] = len( parameters_list )
+ loc_dict[ ObjType + "_param"] = ",\n".join( parameters_list )
+ loc_dict[ ObjType + "_lvars"] = "\n".join( locatedvar_list )
+
+ #----------------------------------------------------------------------
+ # Create the C source files that implement the BACnet server
+ #----------------------------------------------------------------------
+
+ # Names of the .c files that will be generated, based on a template file with same name
+ # (names without '.c' --> this will be added later)
+ # main server.c file is handled separately
+ Generated_BACnet_c_mainfile = "server"
+ Generated_BACnet_c_files = ["ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
+
+ # Names of the .h files that will be generated, based on a template file with same name
+ # (names without '.h' --> this will be added later)
+ Generated_BACnet_h_files = ["server", "device", "config_bacnet_for_beremiz",
+ "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
+ ]
+
+ # Generate the files with the source code
+ postfix = "_".join(map(str, current_location))
+ template_file_dir = os.path.join(os.path.split(__file__)[0],"runtime")
+
+ def generate_file(file_name, extension):
+ generate_file_name = os.path.join(buildpath, "%s_%s.%s"%(file_name,postfix,extension))
+ template_file_name = os.path.join(template_file_dir,"%s.%s"%(file_name,extension))
+ generate_file_content = open(template_file_name).read() % loc_dict
+ generate_file_handle = open(generate_file_name,'w')
+ generate_file_handle .write(generate_file_content)
+ generate_file_handle .close()
+
+ for file_name in Generated_BACnet_c_files:
+ generate_file(file_name, "c")
+ for file_name in Generated_BACnet_h_files:
+ generate_file(file_name, "h")
+ generate_file(Generated_BACnet_c_mainfile, "c")
+ Generated_BACnet_c_mainfile_name = \
+ os.path.join(buildpath, "%s_%s.%s"%(Generated_BACnet_c_mainfile,postfix,"c"))
+
+ #----------------------------------------------------------------------
+ # Finally, define the compilation and linking commands and flags
+ #----------------------------------------------------------------------
+
+ LDFLAGS = []
+ # when using dynamically linked library...
+ #LDFLAGS.append(' -lbacnet')
+ #LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
+ #LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
+ # when using static library:
+ LDFLAGS.append(' "'+os.path.join(BacnetLibraryPath, "libbacnet.a")+'"')
+
+ CFLAGS = ' -I"'+BacnetIncludePath+'"'
+ CFLAGS += ' -I"'+BacnetIncludePortPath+'"'
+
+ return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
+
+
+
+###################################################
+###################################################
+# #
+# R O O T C L A S S #
+# #
+###################################################
+###################################################
+#class RootClass:
+ #XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ #<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ #<xsd:element name="BACnetRoot">
+ #</xsd:element>
+ #</xsd:schema>
+ #"""
+ #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug, "Bacnet Slave")
+ ##,("XXX",_XXXXPlug, "XXX")
+ #]
+
+ #def CTNGenerate_C(self, buildpath, locations):
+ #return [], "", True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_EDE.csv Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,8 @@
+#EDE (Engineering-Data-Exchange) - generated by Beremiz BACnet plugin;;;;;;;;;;;;;;;
+PROJECT_NAME;%(Project Name)s;;;;;;;;;;;;;;
+VERSION_OF_REFERENCEFILE;%(EDE file version)d;;;;;;;;;;;;;;
+TIMESTAMP_OF_LAST_CHANGE;%(Current Time)s;;;;;;;;;;;;;;
+AUTHOR_OF_LAST_CHANGE;%(Project Author)s;;;;;;;;;;;;;;
+VERSION_OF_LAYOUT;2.1;;;;;;;;;;;;;;
+#mandatory;mandatory;mandatory;mandatory;mandatory;optional;optional;optional;optional;optional;optional;optional;optional;optional;optional;optional
+#keyname;device obj.-instance;object-name;object-type;object-instance;description;present-value-default;min-present-value;max-present-value;settable;supports COV;hi-limit;low-limit;state-text-reference;unit-code;vendor-specific-address
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_ObjTypes.csv Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,27 @@
+#Encoding of BACnet Object Types;;;
+#Code;Object Type;;
+0;Analog Input;;
+1;Analog Output;;
+2;Analog Value;;
+3;Binary Input ;;
+4;Binary Output;;
+5;Binary Value;;
+6;Calendar;;
+7;Command;;
+8;Device;;
+9;Event-Enrollment;;
+10;File;;
+11;Group;;
+12;Loop;;
+13;Multistate Input;;
+14;Multistate Output
+15;Notification Class
+16;Program
+17;Schedule
+18;Averaging
+19;Multistate Value
+20;Trend Log
+21;Life Safety Point
+22;Life Safety Zone
+23;Accumulator
+24;Pulse Converter
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_StateTexts.csv Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,2 @@
+#State Text Reference;;;;;;;
+#Reference Number;Text 1;Text 2;Text 3;Text 4;Text 5;...;Text n
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/ede_files/template_Units.csv Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,264 @@
+#Encoding of BACnet Engineering Units;
+#Code;Unit Text
+166;METERS-PER-SECOND-PER-SECOND
+0;SQUARE-METERS
+116;SQUARE-CENTIMETERS
+1;SQUARE-FEET
+115;SQUARE-INCHES
+105;CURRENCY1
+106;CURRENCY2
+107;CURRENCY3
+108;CURRENCY4
+109;CURRENCY5
+110;CURRENCY6
+111;CURRENCY7
+112;CURRENCY8
+113;CURRENCY9
+114;CURRENCY10
+2;MILLIAMPERES
+3;AMPERES
+167;AMPERES-PER-METER
+168;AMPERES-PER-SQUARE-METER
+169;AMPERE-SQUARE-METERS
+199;DECIBELS
+200;DECIBELS-MILLIVOLT
+201;DECIBELS-VOLT
+170;FARADS
+171;HENRYS
+4;OHMS
+237;OHM-METER-SQUARED-PER-METER
+172;OHM-METERS
+145;MILLIOHMS
+122;KILOHMS
+123;MEGOHMS
+190;MICROSIEMENS
+202;MILLISIEMENS
+173;SIEMENS
+174;SIEMENS-PER-METER
+175;TESLAS
+5;VOLTS
+124;MILLIVOLTS
+6;KILOVOLTS
+7;MEGAVOLTS
+8;VOLT-AMPERES
+9;KILOVOLT-AMPERES
+10;MEGAVOLT-AMPERES
+11;VOLT-AMPERES-REACTIVE
+12;KILOVOLT-AMPERES-REACTIVE
+13;MEGAVOLT-AMPERES-REACTIVE
+176;VOLTS-PER-DEGREE-KELVIN
+177;VOLTS-PER-METER
+14;DEGREES-PHASE
+15;POWER-FACTOR
+178;WEBERS
+238;AMPERE-SECONDS
+239;VOLT-AMPERE-HOURS
+240;KILOVOLT-AMPERE-HOURS
+241;MEGAVOLT-AMPERE-HOURS
+242;VOLT-AMPERE-HOURS-REACTIVE
+243;KILOVOLT-AMPERE-HOURS-REACTIVE
+244;MEGAVOLT-AMPERE-HOURS-REACTIVE
+245;VOLT-SQUARE-HOURS
+246;AMPERE-SQUARE-HOURS
+16;JOULES
+17;KILOJOULES
+125;KILOJOULES-PER-KILOGRAM
+126;MEGAJOULES
+18;WATT-HOURS
+19;KILOWATT-HOURS
+146;MEGAWATT-HOURS
+203;WATT-HOURS-REACTIVE
+204;KILOWATT-HOURS-REACTIVE
+205;MEGAWATT-HOURS-REACTIVE
+20;BTUS
+147;KILO-BTUS
+148;MEGA-BTUS
+21;THERMS
+22;TON-HOURS
+23;JOULES-PER-KILOGRAM-DRY-AIR
+149;KILOJOULES-PER-KILOGRAM-DRY-AIR
+150;MEGAJOULES-PER-KILOGRAM-DRY-AIR
+24;BTUS-PER-POUND-DRY-AIR
+117;BTUS-PER-POUND
+127;JOULES-PER-DEGREE-KELVIN
+151;KILOJOULES-PER-DEGREE-KELVIN
+152;MEGAJOULES-PER-DEGREE-KELVIN
+128;JOULES-PER-KILOGRAM-DEGREE-KELVIN
+153;NEWTON
+25;CYCLES-PER-HOUR
+26;CYCLES-PER-MINUTE
+27;HERTZ
+129;KILOHERTZ
+130;MEGAHERTZ
+131;PER-HOUR
+28;GRAMS-OF-WATER-PER-KILOGRAM-DRY-AIR
+29;PERCENT-RELATIVE-HUMIDITY
+194;MICROMETERS
+30;MILLIMETERS
+118;CENTIMETERS
+193;KILOMETERS
+31;METERS
+32;INCHES
+33;FEET
+179;CANDELAS
+180;CANDELAS-PER-SQUARE-METER
+34;WATTS-PER-SQUARE-FOOT
+35;WATTS-PER-SQUARE-METER
+36;LUMENS
+37;LUXES
+38;FOOT-CANDLES
+196;MILLIGRAMS
+195;GRAMS
+39;KILOGRAMS
+40;POUNDS-MASS
+41;TONS
+154;GRAMS-PER-SECOND
+155;GRAMS-PER-MINUTE
+42;KILOGRAMS-PER-SECOND
+43;KILOGRAMS-PER-MINUTE
+44;KILOGRAMS-PER-HOUR
+119;POUNDS-MASS-PER-SECOND
+45;POUNDS-MASS-PER-MINUTE
+46;POUNDS-MASS-PER-HOUR
+156;TONS-PER-HOUR
+132;MILLIWATTS
+47;WATTS
+48;KILOWATTS
+49;MEGAWATTS
+50;BTUS-PER-HOUR
+157;KILO-BTUS-PER-HOUR
+247;JOULE-PER-HOURS
+51;HORSEPOWER
+52;TONS-REFRIGERATION
+53;PASCALS
+133;HECTOPASCALS
+54;KILOPASCALS
+134;MILLIBARS
+55;BARS
+56;POUNDS-FORCE-PER-SQUARE-INCH
+206;MILLIMETERS-OF-WATER
+57;CENTIMETERS-OF-WATER
+58;INCHES-OF-WATER
+59;MILLIMETERS-OF-MERCURY
+60;CENTIMETERS-OF-MERCURY
+61;INCHES-OF-MERCURY
+62;DEGREES-CELSIUS
+63;DEGREES-KELVIN
+181;DEGREES-KELVIN-PER-HOUR
+182;DEGREES-KELVIN-PER-MINUTE
+64;DEGREES-FAHRENHEIT
+65;DEGREE-DAYS-CELSIUS
+66;DEGREE-DAYS-FAHRENHEIT
+120;DELTA-DEGREES-FAHRENHEIT
+121;DELTA-DEGREES-KELVIN
+67;YEARS
+68;MONTHS
+69;WEEKS
+70;DAYS
+71;HOURS
+72;MINUTES
+73;SECONDS
+158;HUNDREDTHS-SECONDS
+159;MILLISECONDS
+160;NEWTON-METERS
+161;MILLIMETERS-PER-SECOND
+162;MILLIMETERS-PER-MINUTE
+74;METERS-PER-SECOND
+163;METERS-PER-MINUTE
+164;METERS-PER-HOUR
+75;KILOMETERS-PER-HOUR
+76;FEET-PER-SECOND
+77;FEET-PER-MINUTE
+78;MILES-PER-HOUR
+79;CUBIC-FEET
+80;CUBIC-METERS
+81;IMPERIAL-GALLONS
+197;MILLILITERS
+82;LITERS
+83;US-GALLONS
+142;CUBIC-FEET-PER-SECOND
+84;CUBIC-FEET-PER-MINUTE
+254;MILLION-STANDARD-CUBIC-FEET-PER-MINUTE
+191;CUBIC-FEET-PER-HOUR
+248;CUBIC-FEET-PER-DAY
+47808;STANDARD-CUBIC-FEET-PER-DAY
+47809;MILLION-STANDARD-CUBIC-FEET-PER-DAY
+47810;THOUSAND-CUBIC-FEET-PER-DAY
+47811;THOUSAND-STANDARD-CUBIC-FEET-PER-DAY
+47812;POUNDS-MASS-PER-DAY
+85;CUBIC-METERS-PER-SECOND
+165;CUBIC-METERS-PER-MINUTE
+135;CUBIC-METERS-PER-HOUR
+249;CUBIC-METERS-PER-DAY
+86;IMPERIAL-GALLONS-PER-MINUTE
+198;MILLILITERS-PER-SECOND
+87;LITERS-PER-SECOND
+88;LITERS-PER-MINUTE
+136;LITERS-PER-HOUR
+89;US-GALLONS-PER-MINUTE
+192;US-GALLONS-PER-HOUR
+90;DEGREES-ANGULAR
+91;DEGREES-CELSIUS-PER-HOUR
+92;DEGREES-CELSIUS-PER-MINUTE
+93;DEGREES-FAHRENHEIT-PER-HOUR
+94;DEGREES-FAHRENHEIT-PER-MINUTE
+183;JOULE-SECONDS
+186;KILOGRAMS-PER-CUBIC-METER
+137;KILOWATT-HOURS-PER-SQUARE-METER
+138;KILOWATT-HOURS-PER-SQUARE-FOOT
+250;WATT-HOURS-PER-CUBIC-METER
+251;JOULES-PER-CUBIC-METER
+139;MEGAJOULES-PER-SQUARE-METER
+140;MEGAJOULES-PER-SQUARE-FOOT
+252;MOLE-PERCENT
+95;NO-UNITS
+187;NEWTON-SECONDS
+188;NEWTONS-PER-METER
+96;PARTS-PER-MILLION
+97;PARTS-PER-BILLION
+253;PASCAL-SECONDS
+98;PERCENT
+143;PERCENT-OBSCURATION-PER-FOOT
+144;PERCENT-OBSCURATION-PER-METER
+99;PERCENT-PER-SECOND
+100;PER-MINUTE
+101;PER-SECOND
+102;PSI-PER-DEGREE-FAHRENHEIT
+103;RADIANS
+184;RADIANS-PER-SECOND
+104;REVOLUTIONS-PER-MINUTE
+185;SQUARE-METERS-PER-NEWTON
+189;WATTS-PER-METER-PER-DEGREE-KELVIN
+141;WATTS-PER-SQUARE-METER-DEGREE-KELVIN
+207;PER-MILLE
+208;GRAMS-PER-GRAM
+209;KILOGRAMS-PER-KILOGRAM
+210;GRAMS-PER-KILOGRAM
+211;MILLIGRAMS-PER-GRAM
+212;MILLIGRAMS-PER-KILOGRAM
+213;GRAMS-PER-MILLILITER
+214;GRAMS-PER-LITER
+215;MILLIGRAMS-PER-LITER
+216;MICROGRAMS-PER-LITER
+217;GRAMS-PER-CUBIC-METER
+218;MILLIGRAMS-PER-CUBIC-METER
+219;MICROGRAMS-PER-CUBIC-METER
+220;NANOGRAMS-PER-CUBIC-METER
+221;GRAMS-PER-CUBIC-CENTIMETER
+222;BECQUERELS
+223;KILOBECQUERELS
+224;MEGABECQUERELS
+225;GRAY
+226;MILLIGRAY
+227;MICROGRAY
+228;SIEVERTS
+229;MILLISIEVERTS
+230;MICROSIEVERTS
+231;MICROSIEVERTS-PER-HOUR
+47814;MILLIREMS
+47815;MILLIREMS-PER-HOUR
+232;DECIBELS-A
+233;NEPHELOMETRIC-TURBIDITY-UNIT
+234;PH
+235;GRAMS-PER-SQUARE-METER
+236;MINUTES-PER-DEGREE-KELVIN
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ai.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,497 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Input Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h> /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "ai_%(locstr)s.h"
+
+
+
+/* initial value for present_value property of each object */
+#define AI_VALUE_INIT (0)
+
+/* The IEC 61131-3 located variables mapped onto the Analog Input objects of BACnet protocol */
+%(AI_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Input Objects */
+#define MAX_ANALOG_INPUTS %(AI_count)s
+static ANALOG_INPUT_DESCR AI_Descr[MAX_ANALOG_INPUTS] = {
+%(AI_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Input_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_UNITS, /* W R (117) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Analog_Input_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ -1
+};
+
+static const int Analog_Input_Properties_Proprietary[] = {
+ -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Analog_Inputs_Init() based off the values
+ * stored in Analog_Input_Properties_Required
+ * Analog_Input_Properties_Optional
+ * Analog_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Input_Properties_List[64];
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Analog_Input_Properties_Required;
+ if (pOptional)
+ *pOptional = Analog_Input_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Analog_Input_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Input_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Analog_Input_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+ Analog_Input_Properties_Required);
+ len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+ Analog_Input_Properties_Optional);
+ len += BACnet_Init_Properties_List(Analog_Input_Properties_List + len,
+ Analog_Input_Properties_Proprietary);
+
+ for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+ // MJS: the following line in th original demo code was commented out so we do not
+ // overwrite the initial values configured by the user in beremiz IDE
+ // memset(&AI_Descr[i], 0x00, sizeof(ANALOG_INPUT_DESCR));
+ AI_Descr[i].Present_Value = AI_VALUE_INIT;
+ AI_Descr[i].Out_Of_Service = 0;
+ AI_Descr[i].Event_State = 0;
+// AI_Descr[i].Units = UNITS_NO_UNITS;
+ }
+ }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Input_Valid_Instance(
+ uint32_t object_instance)
+{
+ return (Analog_Input_Instance_To_Index(object_instance) < MAX_ANALOG_INPUTS);
+}
+
+/* the number of Analog Input Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Analog_Input_Count(void) {return MAX_ANALOG_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Analog_Input_Index_To_Instance(unsigned index) {return AI_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Input_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_ANALOG_INPUTS; index++)
+ if (object_instance == AI_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_ANALOG_INPUTS;
+}
+
+
+
+
+float Analog_Input_Present_Value(
+ uint32_t object_instance)
+{
+ float value = AI_VALUE_INIT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Analog_Input_Instance_To_Index(object_instance);
+ if (index < MAX_ANALOG_INPUTS)
+ value = AI_Descr[index].Present_Value;
+
+ return value;
+}
+
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Input_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_INPUTS)
+ status = characterstring_init_ansi(object_name, AI_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Analog_Input_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Input_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_INPUTS)
+ status = characterstring_init_ansi(object_name, AI_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Analog_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ float real_value = (float) 1.414;
+ unsigned object_index = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ apdu = rpdata->application_data;
+
+ object_index = Analog_Input_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_ANALOG_INPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_ANALOG_INPUT,
+ rpdata->object_instance);
+ break;
+
+ case PROP_OBJECT_NAME:
+ Analog_Input_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_DESCRIPTION:
+ Analog_Input_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_ANALOG_INPUT);
+ break;
+
+ case PROP_PRESENT_VALUE:
+ real_value = Analog_Input_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+
+ case PROP_STATUS_FLAGS:
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ AI_Descr[object_index].Out_Of_Service);
+
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+
+ case PROP_EVENT_STATE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ state = AI_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+
+ case PROP_UNITS:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], AI_Descr[object_index].Units);
+ break;
+
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Analog_Input_Properties_List,
+// property_list_count(Analog_Input_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) &&
+ (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+ ANALOG_INPUT_DESCR *CurrentAI;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ object_index = Analog_Input_Instance_To_Index(wp_data->object_instance);
+ if (object_index < MAX_ANALOG_INPUTS)
+ CurrentAI = &AI_Descr[object_index];
+ else
+ return false;
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ status = false; // not really necessary here.
+ } else {
+ if (!AI_Descr[object_index].Out_Of_Service) {
+ /* input objects can only be written to when Out_Of_Service is true! */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ status = false;
+ } else {
+ AI_Descr[object_index].Present_Value = value.type.Real;
+ status = true;
+ }
+ }
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = CurrentAI->Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAI->Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !CurrentAI->Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ CurrentAI->Present_Value = *(CurrentAI->Located_Var_ptr);
+ }
+ break;
+ }
+
+ case PROP_UNITS:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAI->Units = value.type.Enumerated;
+ }
+ break;
+
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_DESCRIPTION:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+void Analog_Input_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AI_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(AI_Descr[i].Located_Var_ptr) = Analog_Input_Present_Value(AI_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Analog_Input_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AI_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ AI_Descr[i].Present_Value = *(AI_Descr[i].Located_Var_ptr);
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ai.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,102 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AI_H
+#define AI_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct Analog_Input_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ float *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint16_t Units;
+
+ /* stores the current value */
+ /* one entry per priority value */
+ float Present_Value;
+ unsigned Event_State:3;
+ bool Out_Of_Service;
+ } ANALOG_INPUT_DESCR;
+
+
+ void Analog_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Analog_Input_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Analog_Input_Count(
+ void);
+ uint32_t Analog_Input_Index_To_Instance(
+ unsigned index);
+ unsigned Analog_Input_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Analog_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ int Analog_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Analog_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ float Analog_Input_Present_Value(
+ uint32_t object_instance);
+
+ char *Analog_Input_Description(
+ uint32_t instance);
+
+ uint16_t Analog_Input_Units(
+ uint32_t instance);
+
+ bool Analog_Input_Out_Of_Service(
+ uint32_t instance);
+ void Analog_Input_Out_Of_Service_Set(
+ uint32_t instance,
+ bool oos_flag);
+
+ void Analog_Input_Init(
+ void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ao.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,620 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Output Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h> /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "ao_%(locstr)s.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since the values are floats, we use NAN (Not A Number) as our NULL value. */
+/* WARNING: Never use comparisons like 'if (value == AO_LEVEL_NULL)'
+ * as it will always return false, even if both values are NAN.
+ * Use instead the negated version 'if (value != AO_LEVEL_NULL)'
+ * and add an 'else' to the 'if' condition if necessary.
+ * However, some compilers may screw this up if they do not
+ * implement IEEE 754 properly. It is probably best to stick with
+ * the isnan() macro if available.
+ */
+#define AO_VALUE_NULL NAN
+#define AO_VALUE_IS_NULL(x) (isnan(x))
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define AO_VALUE_RELINQUISH_DEFAULT (0.0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Analog Output objects of BACnet protocol */
+%(AO_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Output Objects */
+#define MAX_ANALOG_OUTPUTS %(AO_count)s
+static ANALOG_OUTPUT_DESCR AO_Descr[MAX_ANALOG_OUTPUTS] = {
+%(AO_param)s
+};
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Output_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W W ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_UNITS, /* W R (117) */
+ PROP_PRIORITY_ARRAY, /* R R ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R R (104) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R R (431) */
+ -1
+};
+
+static const int Analog_Output_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ -1
+};
+
+static const int Analog_Output_Properties_Proprietary[] = {
+ -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Analog_Outputs_Init() based off the values
+ * stored in Analog_Output_Properties_Required
+ * Analog_Output_Properties_Optional
+ * Analog_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Output_Properties_List[64];
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Analog_Output_Properties_Required;
+ if (pOptional)
+ *pOptional = Analog_Output_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Analog_Output_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Output_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Analog_Output_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+ Analog_Output_Properties_Required);
+ len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+ Analog_Output_Properties_Optional);
+ len += BACnet_Init_Properties_List(Analog_Output_Properties_List + len,
+ Analog_Output_Properties_Proprietary);
+
+ for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+ // MJS: the following line in th original demo code was commented out so we do not
+ // overwrite the initial values configured by the user in beremiz IDE
+ // memset(&AO_Descr[i], 0x00, sizeof(ANALOG_OUTPUT_DESCR));
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ AO_Descr[i].Present_Value[j] = AO_VALUE_NULL;
+ }
+ AO_Descr[i].Out_Of_Service = 0;
+ AO_Descr[i].Event_State = 0;
+// AO_Descr[i].Units = UNITS_NO_UNITS;
+ }
+ }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Output_Valid_Instance(
+ uint32_t object_instance)
+{
+ return (Analog_Output_Instance_To_Index(object_instance) < MAX_ANALOG_OUTPUTS);
+}
+
+/* the number of Analog Output Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Analog_Output_Count(void) {return MAX_ANALOG_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Analog_Output_Index_To_Instance(unsigned index) {return AO_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Output_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_ANALOG_OUTPUTS; index++)
+ if (object_instance == AO_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_ANALOG_OUTPUTS;
+}
+
+
+
+
+float Analog_Output_Present_Value(
+ uint32_t object_instance)
+{
+ float value = AO_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Analog_Output_Instance_To_Index(object_instance);
+ if (index < MAX_ANALOG_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!AO_VALUE_IS_NULL(AO_Descr[index].Present_Value[i])) {
+ value = AO_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Analog_Output_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Analog_Output_Instance_To_Index(object_instance);
+ if (index < MAX_ANALOG_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!AO_VALUE_IS_NULL(AO_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+/* For a given object instance-number, sets the present-value at a given
+ * priority 1..16 (except 6, see ASHRAE 135-2016, section 19.2.2)
+ */
+bool Analog_Output_Present_Value_Set(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority)
+{
+ unsigned index = 0;
+
+ index = Analog_Output_Instance_To_Index(object_instance);
+ if (index >= MAX_ANALOG_OUTPUTS)
+ return false;
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ AO_Descr[index].Present_Value[priority] = value;
+ return true;
+}
+
+
+
+bool Analog_Output_Present_Value_Relinquish(
+ uint32_t object_instance,
+ unsigned priority)
+{
+ unsigned index = 0;
+
+ index = Analog_Output_Instance_To_Index(object_instance);
+ if (index >= MAX_ANALOG_OUTPUTS)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ AO_Descr[index].Present_Value[priority] = AO_VALUE_NULL;
+ return true;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Output_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_OUTPUTS)
+ status = characterstring_init_ansi(object_name, AO_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Analog_Output_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Output_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_OUTPUTS)
+ status = characterstring_init_ansi(object_name, AO_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Analog_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ float real_value = (float) 1.414;
+ unsigned object_index = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ apdu = rpdata->application_data;
+
+ object_index = Analog_Output_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_ANALOG_OUTPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_ANALOG_OUTPUT,
+ rpdata->object_instance);
+ break;
+
+ case PROP_OBJECT_NAME:
+ Analog_Output_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_DESCRIPTION:
+ Analog_Output_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_ANALOG_OUTPUT);
+ break;
+
+ case PROP_PRESENT_VALUE:
+ real_value = Analog_Output_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+
+ case PROP_STATUS_FLAGS:
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ AO_Descr[object_index].Out_Of_Service);
+
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+
+ case PROP_EVENT_STATE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ state = AO_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(AO_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ AO_VALUE_IS_NULL,
+ encode_application_real)
+ break;
+
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Analog_Output_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+
+ case PROP_RELINQUISH_DEFAULT:
+ real_value = AO_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+
+ case PROP_UNITS:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], AO_Descr[object_index].Units);
+ break;
+
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Analog_Output_Properties_List,
+// property_list_count(Analog_Output_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+ (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+ ANALOG_OUTPUT_DESCR *CurrentAO;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ object_index = Analog_Output_Instance_To_Index(wp_data->object_instance);
+ if (object_index < MAX_ANALOG_OUTPUTS)
+ CurrentAO = &AO_Descr[object_index];
+ else
+ return false;
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ if (value.tag == BACNET_APPLICATION_TAG_REAL) {
+ if (Analog_Output_Present_Value_Set(wp_data->object_instance,
+ value.type.Real, wp_data->priority)) {
+ status = true;
+ } else if (wp_data->priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Analog_Output_Present_Value_Relinquish
+ (wp_data->object_instance, wp_data->priority);
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = CurrentAO->Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAO->Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !CurrentAO->Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ CurrentAO->Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(CurrentAO->Located_Var_ptr);
+ }
+ break;
+ }
+
+ case PROP_UNITS:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAO->Units = value.type.Enumerated;
+ }
+ break;
+
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_DESCRIPTION:
+ case PROP_RELINQUISH_DEFAULT:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_PROPERTY_LIST:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+void Analog_Output_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AO_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(AO_Descr[i].Located_Var_ptr) = Analog_Output_Present_Value(AO_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Analog_Output_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AO_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ AO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(AO_Descr[i].Located_Var_ptr);
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/ao.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,106 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AO_H
+#define AO_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct Analog_Output_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ float *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint16_t Units;
+
+ /* stores the current value */
+ /* one entry per priority value */
+ float Present_Value[BACNET_MAX_PRIORITY];
+ unsigned Event_State:3;
+ bool Out_Of_Service;
+ } ANALOG_OUTPUT_DESCR;
+
+
+ void Analog_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Analog_Output_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Analog_Output_Count(
+ void);
+ uint32_t Analog_Output_Index_To_Instance(
+ unsigned index);
+ unsigned Analog_Output_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Analog_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ int Analog_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Analog_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ bool Analog_Output_Present_Value_Set(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority);
+ float Analog_Output_Present_Value(
+ uint32_t object_instance);
+
+ char *Analog_Output_Description(
+ uint32_t instance);
+
+ uint16_t Analog_Output_Units(
+ uint32_t instance);
+
+ bool Analog_Output_Out_Of_Service(
+ uint32_t instance);
+ void Analog_Output_Out_Of_Service_Set(
+ uint32_t instance,
+ bool oos_flag);
+
+ void Analog_Output_Init(
+ void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/av.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,622 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Analog Value Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h> /* NAN maro */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "bactext.h"
+#include "device_%(locstr)s.h"
+#include "handlers.h"
+#include "av_%(locstr)s.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since the values are floats, we use NAN (Not A Number) as our NULL value. */
+/* WARNING: Never use comparisons like 'if (value == AO_LEVEL_NULL)'
+ * as it will always return false, even if both values are NAN.
+ * Use instead the negated version 'if (value != AO_LEVEL_NULL)'
+ * and add an 'else' to the 'if' condition if necessary.
+ * However, some compilers may screw this up if they do not
+ * implement IEEE 754 properly. It is probably best to stick with
+ * the isnan() macro if available.
+ */
+#define AV_VALUE_NULL NAN
+#define AV_VALUE_IS_NULL(x) (isnan(x))
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define AV_VALUE_RELINQUISH_DEFAULT (0.0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Analog Value objects of BACnet protocol */
+%(AV_lvars)s
+
+
+/* The array where we keep all the state related to the Analog Value Objects */
+#define MAX_ANALOG_VALUES %(AV_count)s
+static ANALOG_VALUE_DESCR AV_Descr[MAX_ANALOG_VALUES] = {
+%(AV_param)s
+};
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Analog_Value_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_UNITS, /* W R (117) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Analog_Value_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ /* required if Present_Value is writable (which is true in our case!) */
+ PROP_PRIORITY_ARRAY, /* R O ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R O (104) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R O (431) */
+ -1
+};
+
+static const int Analog_Value_Properties_Proprietary[] = {
+ -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Analog_Values_Init() based off the values
+ * stored in Analog_Value_Properties_Required
+ * Analog_Value_Properties_Optional
+ * Analog_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Analog_Value_Properties_List[64];
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Analog_Value_Properties_Required;
+ if (pOptional)
+ *pOptional = Analog_Value_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Analog_Value_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Analog_Value_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Analog_Value_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+ Analog_Value_Properties_Required);
+ len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+ Analog_Value_Properties_Optional);
+ len += BACnet_Init_Properties_List(Analog_Value_Properties_List + len,
+ Analog_Value_Properties_Proprietary);
+
+ for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+ // MJS: the following line in th original demo code was commented out so we do not
+ // overwrite the initial values configured by the user in beremiz IDE
+ // memset(&AV_Descr[i], 0x00, sizeof(ANALOG_VALUE_DESCR));
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ AV_Descr[i].Present_Value[j] = AV_VALUE_NULL;
+ }
+ AV_Descr[i].Out_Of_Service = 0;
+ AV_Descr[i].Event_State = 0;
+// AV_Descr[i].Units = UNITS_NO_UNITS;
+ }
+ }
+}
+
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Value_Valid_Instance(
+ uint32_t object_instance)
+{
+ return (Analog_Value_Instance_To_Index(object_instance) < MAX_ANALOG_VALUES);
+}
+
+/* the number of Analog Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Analog_Value_Count(void) {return MAX_ANALOG_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Analog_Value_Index_To_Instance(unsigned index) {return AV_Descr[index].Object_Identifier;}
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Analog_Value_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_ANALOG_VALUES; index++)
+ if (object_instance == AV_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_ANALOG_VALUES;
+}
+
+
+
+
+float Analog_Value_Present_Value(
+ uint32_t object_instance)
+{
+ float value = AV_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Analog_Value_Instance_To_Index(object_instance);
+ if (index < MAX_ANALOG_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!AV_VALUE_IS_NULL(AV_Descr[index].Present_Value[i])) {
+ value = AV_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Analog_Value_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Analog_Value_Instance_To_Index(object_instance);
+ if (index < MAX_ANALOG_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!AV_VALUE_IS_NULL(AV_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+/* For a given object instance-number, sets the present-value at a given
+ * priority 1..16 (except 6, see ASHRAE 135-2016, section 19.2.2)
+ */
+bool Analog_Value_Present_Value_Set(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority)
+{
+ unsigned index = 0;
+
+ index = Analog_Value_Instance_To_Index(object_instance);
+ if (index >= MAX_ANALOG_VALUES)
+ return false;
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ AV_Descr[index].Present_Value[priority] = value;
+ return true;
+}
+
+
+
+bool Analog_Value_Present_Value_Relinquish(
+ uint32_t object_instance,
+ unsigned priority)
+{
+ unsigned index = 0;
+
+ index = Analog_Value_Instance_To_Index(object_instance);
+ if (index >= MAX_ANALOG_VALUES)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ AV_Descr[index].Present_Value[priority] = AV_VALUE_NULL;
+ return true;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Value_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_VALUES)
+ status = characterstring_init_ansi(object_name, AV_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Analog_Value_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Analog_Value_Instance_To_Index(object_instance);
+
+ if (index < MAX_ANALOG_VALUES)
+ status = characterstring_init_ansi(object_name, AV_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Analog_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ float real_value = (float) 1.414;
+ unsigned object_index = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+ ANALOG_VALUE_DESCR *CurrentAV;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ apdu = rpdata->application_data;
+
+ object_index = Analog_Value_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_ANALOG_VALUES) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_ANALOG_VALUE,
+ rpdata->object_instance);
+ break;
+
+ case PROP_OBJECT_NAME:
+ Analog_Value_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_DESCRIPTION:
+ Analog_Value_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_ANALOG_VALUE);
+ break;
+
+ case PROP_PRESENT_VALUE:
+ real_value = Analog_Value_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+
+ case PROP_STATUS_FLAGS:
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ AV_Descr[object_index].Out_Of_Service);
+
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+
+ case PROP_EVENT_STATE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ state = AV_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(AV_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ AV_VALUE_IS_NULL,
+ encode_application_real)
+ break;
+
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Analog_Value_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+
+ case PROP_RELINQUISH_DEFAULT:
+ real_value = AV_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+
+ case PROP_UNITS:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], AV_Descr[object_index].Units);
+ break;
+
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Analog_Value_Properties_List,
+// property_list_count(Analog_Value_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+ (rpdata->object_property != PROP_EVENT_TIME_STAMPS) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Analog_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+ ANALOG_VALUE_DESCR *CurrentAV;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->object_property != PROP_EVENT_TIME_STAMPS) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ object_index = Analog_Value_Instance_To_Index(wp_data->object_instance);
+ if (object_index < MAX_ANALOG_VALUES)
+ CurrentAV = &AV_Descr[object_index];
+ else
+ return false;
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ if (value.tag == BACNET_APPLICATION_TAG_REAL) {
+ if (Analog_Value_Present_Value_Set(wp_data->object_instance,
+ value.type.Real, wp_data->priority)) {
+ status = true;
+ } else if (wp_data->priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Analog_Value_Present_Value_Relinquish
+ (wp_data->object_instance, wp_data->priority);
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = CurrentAV->Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAV->Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !CurrentAV->Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ CurrentAV->Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(CurrentAV->Located_Var_ptr);
+ }
+ break;
+ }
+
+ case PROP_UNITS:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ CurrentAV->Units = value.type.Enumerated;
+ }
+ break;
+
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_DESCRIPTION:
+ case PROP_RELINQUISH_DEFAULT:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_PROPERTY_LIST:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+void Analog_Value_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(AV_Descr[i].Located_Var_ptr) = Analog_Value_Present_Value(AV_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Analog_Value_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_ANALOG_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (AV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ AV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(AV_Descr[i].Located_Var_ptr);
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/av.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,106 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef AV_H
+#define AV_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "wp.h"
+#include "rp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct analog_value_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ float *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint16_t Units;
+
+ /* stores the current value */
+ /* one entry per priority value */
+ float Present_Value[BACNET_MAX_PRIORITY];
+ unsigned Event_State:3;
+ bool Out_Of_Service;
+ } ANALOG_VALUE_DESCR;
+
+
+ void Analog_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Analog_Value_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Analog_Value_Count(
+ void);
+ uint32_t Analog_Value_Index_To_Instance(
+ unsigned index);
+ unsigned Analog_Value_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Analog_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ int Analog_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Analog_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ bool Analog_Value_Present_Value_Set(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority);
+ float Analog_Value_Present_Value(
+ uint32_t object_instance);
+
+ char *Analog_Value_Description(
+ uint32_t instance);
+
+ uint16_t Analog_Value_Units(
+ uint32_t instance);
+
+ bool Analog_Value_Out_Of_Service(
+ uint32_t instance);
+ void Analog_Value_Out_Of_Service_Set(
+ uint32_t instance,
+ bool oos_flag);
+
+ void Analog_Value_Init(
+ void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bi.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,492 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Input Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bi_%(locstr)s.h"
+#include "handlers.h"
+
+
+
+/* initial value for present_value property of each object */
+#define BI_VALUE_INIT (0)
+
+
+/* The IEC 61131-3 located variables mapped onto the Binary Input objects of BACnet protocol */
+%(BI_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Input Objects */
+#define MAX_BINARY_INPUTS %(BI_count)s
+static BINARY_INPUT_DESCR BI_Descr[MAX_BINARY_INPUTS] = {
+%(BI_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Input_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_POLARITY, /* R R ( 84) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Binary_Input_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ -1
+};
+
+static const int Binary_Input_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Binary_Input_Init() based off the values
+ * stored in Binary_Input_Properties_Required
+ * Binary_Input_Properties_Optional
+ * Binary_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Input_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Binary_Input_Properties_Required;
+ if (pOptional)
+ *pOptional = Binary_Input_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Binary_Input_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Input_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Input_Init() called!\n");
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Binary_Input_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+ Binary_Input_Properties_Required);
+ len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+ Binary_Input_Properties_Optional);
+ len += BACnet_Init_Properties_List(Binary_Input_Properties_List + len,
+ Binary_Input_Properties_Proprietary);
+
+ /* initialize all the binary values priority arrays to NULL */
+ for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+ BI_Descr[i].Present_Value = BI_VALUE_INIT;
+ BI_Descr[i].Polarity = POLARITY_NORMAL;
+ }
+ }
+
+ return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Input_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Binary_Input_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Binary_Input_Instance_To_Index(object_instance) < MAX_BINARY_INPUTS);
+}
+
+
+/* the number of Binary Input Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Binary_Input_Count(void) {return MAX_BINARY_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Binary_Input_Index_To_Instance(unsigned index) {return BI_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Input_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_BINARY_INPUTS; index++)
+ if (object_instance == BI_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_BINARY_INPUTS;
+}
+
+
+
+BACNET_BINARY_PV Binary_Input_Present_Value(
+ uint32_t object_instance)
+{
+ BACNET_BINARY_PV value = BI_VALUE_INIT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Input_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+ index = Binary_Input_Instance_To_Index(object_instance);
+ if (index < MAX_BINARY_INPUTS)
+ value = BI_Descr[index].Present_Value;
+
+ return value;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Input_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_INPUTS)
+ status = characterstring_init_ansi(object_name, BI_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Binary_Input_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Input_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_INPUTS)
+ status = characterstring_init_ansi(object_name, BI_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Binary_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Input_Read_Property() called!\n");
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Binary_Input_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_BINARY_INPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_BINARY_INPUT,
+ rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Binary_Input_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ Binary_Input_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_BINARY_INPUT);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Binary_Input_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ state = BI_Descr[object_index].Out_Of_Service;
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = BI_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_POLARITY:
+ apdu_len = encode_application_enumerated(&apdu[0],
+ BI_Descr[object_index].Polarity);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Binary_Input_Properties_List,
+// property_list_count(Binary_Input_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ unsigned int priority = 0;
+ BACNET_BINARY_PV level = BINARY_NULL;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ /* only array properties can have array options */
+ if (wp_data->array_index != BACNET_ARRAY_ALL) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Binary_Input_Write_Property() is called
+ */
+ object_index = Binary_Input_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+ &wp_data->error_class, &wp_data->error_code);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ } else {
+ if (!BI_Descr[object_index].Out_Of_Service) {
+ /* input objects can only be written to when Out_Of_Service is true! */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ status = false; // not really necessary here.
+ } else {
+ if (!(value.type.Enumerated <= MAX_BINARY_PV)) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ status = false;
+ } else {
+ level = (BACNET_BINARY_PV) value.type.Enumerated;
+ BI_Descr[object_index].Present_Value = level;
+ status = true;
+ }
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = BI_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ BI_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !BI_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ BI_Descr[object_index].Present_Value =
+ *(BI_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_DESCRIPTION:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_POLARITY:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Binary_Input_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BI_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(BI_Descr[i].Located_Var_ptr) = Binary_Input_Present_Value(BI_Descr[i].Object_Identifier);
+ }
+}
+
+void Binary_Input_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BI_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value (0 is false, all other values are true)
+ if (*(BI_Descr[i].Located_Var_ptr))
+ BI_Descr[i].Present_Value = BINARY_ACTIVE;
+ else
+ BI_Descr[i].Present_Value = BINARY_INACTIVE;
+ }
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bi.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,104 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BI_H
+#define BI_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct Binary_Input_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ /* stores the current value */
+ BACNET_BINARY_PV Present_Value;
+ /* Writable out-of-service allows others to play with our Present Value */
+ /* without changing the physical output */
+ bool Out_Of_Service;
+ BACNET_POLARITY Polarity;
+ } BINARY_INPUT_DESCR;
+
+
+ void Binary_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Binary_Input_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Binary_Input_Count(
+ void);
+ uint32_t Binary_Input_Index_To_Instance(
+ unsigned index);
+ unsigned Binary_Input_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Binary_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ char *Binary_Input_Description(
+ uint32_t instance);
+
+ BACNET_BINARY_PV Binary_Input_Present_Value(
+ uint32_t instance);
+ bool Binary_Input_Present_Value_Set(
+ uint32_t instance,
+ BACNET_BINARY_PV value);
+
+ bool Binary_Input_Out_Of_Service(
+ uint32_t instance);
+ void Binary_Input_Out_Of_Service_Set(
+ uint32_t instance,
+ bool value);
+
+ char *Binary_Input_Description(
+ uint32_t instance);
+
+ int Binary_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Binary_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ void Binary_Input_Init(
+ void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bo.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,567 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Output Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bo_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+// BINARY_NULL
+/* test whether value is NULL */
+#define BINARY_OUTPUT_IS_NULL(x) ((x) == BINARY_NULL)
+
+/* When all the priorities are level null, the present value returns
+ * the Relinquish Default value
+ */
+#define BO_VALUE_RELINQUISH_DEFAULT BINARY_INACTIVE
+
+/* The IEC 61131-3 located variables mapped onto the Binary Output objects of BACnet protocol */
+%(BO_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Output Objects */
+#define MAX_BINARY_OUTPUTS %(BO_count)s
+static BINARY_OUTPUT_DESCR BO_Descr[MAX_BINARY_OUTPUTS] = {
+%(BO_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Output_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W W ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_POLARITY, /* R R ( 84) */
+ PROP_PRIORITY_ARRAY, /* R R ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R R (104) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R R (431) */
+ -1
+};
+
+static const int Binary_Output_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ -1
+};
+
+static const int Binary_Output_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Binary_Output_Init() based off the values
+ * stored in Binary_Output_Properties_Required
+ * Binary_Output_Properties_Optional
+ * Binary_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Output_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Binary_Output_Properties_Required;
+ if (pOptional)
+ *pOptional = Binary_Output_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Binary_Output_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Output_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Output_Init() called!\n");
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Binary_Output_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+ Binary_Output_Properties_Required);
+ len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+ Binary_Output_Properties_Optional);
+ len += BACnet_Init_Properties_List(Binary_Output_Properties_List + len,
+ Binary_Output_Properties_Proprietary);
+
+ /* initialize all the binary values priority arrays to NULL */
+ for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ BO_Descr[i].Present_Value[j] = BINARY_NULL;
+ }
+ }
+ }
+
+ return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Output_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Binary_Output_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Binary_Output_Instance_To_Index(object_instance) < MAX_BINARY_OUTPUTS);
+}
+
+
+/* the number of Binary Output Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Binary_Output_Count(void) {return MAX_BINARY_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Binary_Output_Index_To_Instance(unsigned index) {return BO_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Output_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_BINARY_OUTPUTS; index++)
+ if (object_instance == BO_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_BINARY_OUTPUTS;
+}
+
+
+
+BACNET_BINARY_PV Binary_Output_Present_Value(
+ uint32_t object_instance)
+{
+ BACNET_BINARY_PV value = BO_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Output_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+ index = Binary_Output_Instance_To_Index(object_instance);
+ if (index < MAX_BINARY_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!BINARY_OUTPUT_IS_NULL(BO_Descr[index].Present_Value[i])) {
+ value = BO_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Binary_Output_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Binary_Output_Instance_To_Index(object_instance);
+ if (index < MAX_BINARY_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!BINARY_OUTPUT_IS_NULL(BO_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Output_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_OUTPUTS)
+ status = characterstring_init_ansi(object_name, BO_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Binary_Output_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Output_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_OUTPUTS)
+ status = characterstring_init_ansi(object_name, BO_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Binary_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Output_Read_Property() called!\n");
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Binary_Output_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_BINARY_OUTPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_BINARY_OUTPUT,
+ rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Binary_Output_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ Binary_Output_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Binary_Output_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ state = BO_Descr[object_index].Out_Of_Service;
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = BO_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(BO_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ BINARY_OUTPUT_IS_NULL,
+ encode_application_enumerated)
+ break;
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Binary_Output_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+
+ case PROP_RELINQUISH_DEFAULT:
+ present_value = BO_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_enumerated(&apdu[0], present_value);
+ break;
+ case PROP_POLARITY:
+ apdu_len = encode_application_enumerated(&apdu[0],
+ BO_Descr[object_index].Polarity);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Binary_Output_Properties_List,
+// property_list_count(Binary_Output_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ unsigned int priority = 0;
+ BACNET_BINARY_PV level = BINARY_NULL;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ /* only array properties can have array options */
+ if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Binary_Output_Write_Property() is called
+ */
+ object_index = Binary_Output_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
+ priority = wp_data->priority;
+ if (priority && (priority <= BACNET_MAX_PRIORITY) &&
+ (priority != 6 /* reserved */ ) &&
+ (value.type.Enumerated <= MAX_BINARY_PV)) {
+ level = (BACNET_BINARY_PV) value.type.Enumerated;
+ priority--;
+ BO_Descr[object_index].Present_Value[priority] = level;
+ status = true;
+ } else if (priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ level = BINARY_NULL;
+ priority = wp_data->priority;
+ if (priority && (priority <= BACNET_MAX_PRIORITY)) {
+ priority--;
+ BO_Descr[object_index].Present_Value[priority] = level;
+ } else {
+ status = false;
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = BO_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ BO_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !BO_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ BO_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(BO_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_DESCRIPTION:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ case PROP_RELINQUISH_DEFAULT:
+ case PROP_POLARITY:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Binary_Output_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BO_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(BO_Descr[i].Located_Var_ptr) = Binary_Output_Present_Value(BO_Descr[i].Object_Identifier);
+ }
+}
+
+void Binary_Output_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BO_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(BO_Descr[i].Located_Var_ptr);
+
+ // If the Present_Value was set to an invalid value (i.e. > 1, and < BINARY_NULL)
+ // then we set it to BINARY_ACTIVE
+ // (i.e. we assume 0 is FALSE, all other non NULL values are TRUE)
+ if ((BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_INACTIVE) &&
+ (BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_ACTIVE ) &&
+ (BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_NULL ))
+ BO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = BINARY_ACTIVE;
+ }
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bo.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,105 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BO_H
+#define BO_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct Binary_Output_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ /* stores the current value */
+ /* one entry per priority value */
+ BACNET_BINARY_PV Present_Value[BACNET_MAX_PRIORITY];
+ /* Writable out-of-service allows others to play with our Present Value */
+ /* without changing the physical output */
+ bool Out_Of_Service;
+ BACNET_POLARITY Polarity;
+ } BINARY_OUTPUT_DESCR;
+
+
+ void Binary_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Binary_Output_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Binary_Output_Count(
+ void);
+ uint32_t Binary_Output_Index_To_Instance(
+ unsigned index);
+ unsigned Binary_Output_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Binary_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ char *Binary_Output_Description(
+ uint32_t instance);
+
+ BACNET_BINARY_PV Binary_Output_Present_Value(
+ uint32_t instance);
+ bool Binary_Output_Present_Value_Set(
+ uint32_t instance,
+ BACNET_BINARY_PV value);
+
+ bool Binary_Output_Out_Of_Service(
+ uint32_t instance);
+ void Binary_Output_Out_Of_Service_Set(
+ uint32_t instance,
+ bool value);
+
+ char *Binary_Output_Description(
+ uint32_t instance);
+
+ int Binary_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Binary_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ void Binary_Output_Init(
+ void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bv.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,562 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Binary Value Objects - customize for your use */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "wp.h"
+#include "rp.h"
+#include "bv_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+// BINARY_NULL
+/* test whether value is NULL */
+#define BINARY_VALUE_IS_NULL(x) ((x) == BINARY_NULL)
+
+/* When all the priorities are level null, the present value returns
+ * the Relinquish Default value
+ */
+#define BV_VALUE_RELINQUISH_DEFAULT BINARY_INACTIVE
+
+/* The IEC 61131-3 located variables mapped onto the Binary Value objects of BACnet protocol */
+%(BV_lvars)s
+
+
+/* The array where we keep all the state related to the Binary Value Objects */
+#define MAX_BINARY_VALUES %(BV_count)s
+static BINARY_VALUE_DESCR BV_Descr[MAX_BINARY_VALUES] = {
+%(BV_param)s
+};
+
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Binary_Value_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Binary_Value_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ /* required if Present_Value is writable (which is true in our case!) */
+ PROP_PRIORITY_ARRAY, /* R O ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R O (104) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R O (431) */
+ -1
+};
+
+static const int Binary_Value_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Binary_Value_Init() based off the values
+ * stored in Binary_Value_Properties_Required
+ * Binary_Value_Properties_Optional
+ * Binary_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Binary_Value_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Binary_Value_Properties_Required;
+ if (pOptional)
+ *pOptional = Binary_Value_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Binary_Value_Properties_Proprietary;
+
+ return;
+}
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Binary_Value_Init(
+ void)
+{
+ unsigned i, j;
+ static bool initialized = false;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Value_Init() called!\n");
+
+ if (!initialized) {
+ initialized = true;
+
+ /* initialize the Binary_Value_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+ Binary_Value_Properties_Required);
+ len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+ Binary_Value_Properties_Optional);
+ len += BACnet_Init_Properties_List(Binary_Value_Properties_List + len,
+ Binary_Value_Properties_Proprietary);
+
+ /* initialize all the binary values priority arrays to NULL */
+ for (i = 0; i < MAX_BINARY_VALUES; i++) {
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ BV_Descr[i].Present_Value[j] = BINARY_NULL;
+ }
+ }
+ }
+
+ return;
+}
+
+
+
+/* validate that the given instance (Object ID) exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Value_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Binary_Value_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Binary_Value_Instance_To_Index(object_instance) < MAX_BINARY_VALUES);
+}
+
+
+/* the number of Binary Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Binary_Value_Count(void) {return MAX_BINARY_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Binary_Value_Index_To_Instance(unsigned index) {return BV_Descr[index].Object_Identifier;}
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Binary_Value_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_BINARY_VALUES; index++)
+ if (object_instance == BV_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_BINARY_VALUES;
+}
+
+
+
+BACNET_BINARY_PV Binary_Value_Present_Value(
+ uint32_t object_instance)
+{
+ BACNET_BINARY_PV value = BV_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0;
+ unsigned i = 0;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Value_Present_Value(obj_ID=%%u) called!\n", object_instance);
+
+ index = Binary_Value_Instance_To_Index(object_instance);
+ if (index < MAX_BINARY_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!BINARY_VALUE_IS_NULL(BV_Descr[index].Present_Value[i])) {
+ value = BV_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Binary_Value_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Binary_Value_Instance_To_Index(object_instance);
+ if (index < MAX_BINARY_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!BINARY_VALUE_IS_NULL(BV_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+/* note: the object name must be unique within this device */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Value_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_VALUES)
+ status = characterstring_init_ansi(object_name, BV_Descr[index].Object_Name);
+
+ return status;
+}
+
+
+
+bool Binary_Value_Object_Description(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+ unsigned index = Binary_Value_Instance_To_Index(object_instance);
+
+ if (index < MAX_BINARY_VALUES)
+ status = characterstring_init_ansi(object_name, BV_Descr[index].Description);
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Binary_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ BACNET_BINARY_PV present_value = BINARY_INACTIVE;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ // fprintf(stderr, "BACnet plugin: Binary_Value_Read_Property() called!\n");
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Binary_Value_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_BINARY_VALUES) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_BINARY_VALUE,
+ rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Binary_Value_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ Binary_Value_Object_Description(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0], OBJECT_BINARY_VALUE);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Binary_Value_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ state = BV_Descr[object_index].Out_Of_Service;
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = BV_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(BV_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ BINARY_VALUE_IS_NULL,
+ encode_application_enumerated)
+ break;
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Binary_Value_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+
+ case PROP_RELINQUISH_DEFAULT:
+ present_value = BV_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_enumerated(&apdu[0], present_value);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Binary_Value_Properties_List,
+// property_list_count(Binary_Value_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+
+
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Binary_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ unsigned int object_index = 0;
+ unsigned int priority = 0;
+ BACNET_BINARY_PV level = BINARY_NULL;
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ /* only array properties can have array options */
+ if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Binary_Value_Write_Property() is called
+ */
+ object_index = Binary_Value_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
+ priority = wp_data->priority;
+ if (priority && (priority <= BACNET_MAX_PRIORITY) &&
+ (priority != 6 /* reserved */ ) &&
+ (value.type.Enumerated <= MAX_BINARY_PV)) {
+ level = (BACNET_BINARY_PV) value.type.Enumerated;
+ priority--;
+ BV_Descr[object_index].Present_Value[priority] = level;
+ status = true;
+ } else if (priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ level = BINARY_NULL;
+ priority = wp_data->priority;
+ if (priority && (priority <= BACNET_MAX_PRIORITY)) {
+ priority--;
+ BV_Descr[object_index].Present_Value[priority] = level;
+ } else {
+ status = false;
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = BV_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ BV_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !BV_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ BV_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(BV_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_DESCRIPTION:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ case PROP_RELINQUISH_DEFAULT:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Binary_Value_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(BV_Descr[i].Located_Var_ptr) = Binary_Value_Present_Value(BV_Descr[i].Object_Identifier);
+ }
+}
+
+void Binary_Value_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_BINARY_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (BV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(BV_Descr[i].Located_Var_ptr);
+
+ // If the Present_Value was set to an invalid value (i.e. > 1, and < BINARY_NULL)
+ // then we set it to BINARY_ACTIVE
+ // (i.e. we assume 0 is FALSE, all other non NULL values are TRUE)
+ if ((BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_INACTIVE) &&
+ (BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_ACTIVE ) &&
+ (BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] != BINARY_NULL ))
+ BV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = BINARY_ACTIVE;
+ }
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/bv.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,104 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef BV_H
+#define BV_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ typedef struct binary_value_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ /* stores the current value */
+ /* one entry per priority value */
+ BACNET_BINARY_PV Present_Value[BACNET_MAX_PRIORITY];
+ /* Writable out-of-service allows others to play with our Present Value */
+ /* without changing the physical output */
+ bool Out_Of_Service;
+ } BINARY_VALUE_DESCR;
+
+
+ void Binary_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ bool Binary_Value_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Binary_Value_Count(
+ void);
+ uint32_t Binary_Value_Index_To_Instance(
+ unsigned index);
+ unsigned Binary_Value_Instance_To_Index(
+ uint32_t object_instance);
+
+ bool Binary_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ char *Binary_Value_Description(
+ uint32_t instance);
+
+ BACNET_BINARY_PV Binary_Value_Present_Value(
+ uint32_t instance);
+ bool Binary_Value_Present_Value_Set(
+ uint32_t instance,
+ BACNET_BINARY_PV value);
+
+ bool Binary_Value_Out_Of_Service(
+ uint32_t instance);
+ void Binary_Value_Out_Of_Service_Set(
+ uint32_t instance,
+ bool value);
+
+ char *Binary_Value_Description(
+ uint32_t instance);
+
+ int Binary_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Binary_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ void Binary_Value_Init(
+ void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/config_bacnet_for_beremiz.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,284 @@
+/**************************************************************************
+*
+* Copyright (C) 2004 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+
+#ifndef CONFIG_BACNET_FOR_BEREMIZ_H
+#define CONFIG_BACNET_FOR_BEREMIZ_H
+
+#ifdef CONFIG_H
+#error "config.h already processed! (config_bacnet_for_beremiz.h should be included before config.h)"
+#endif
+
+/* Compilaton options for BACnet library, configured for the BACnet sserver
+ * running on Beremiz.
+ */
+
+/* declare a single physical layer using your compiler define.
+ * see datalink.h for possible defines.
+ */
+/* Use BACnet/IP */
+#define BACDL_BIP
+
+/* optional configuration for BACnet/IP datalink layers */
+/* other BIP defines (define as 1 to enable):
+ USE_INADDR - uses INADDR_BROADCAST for broadcast and binds using INADDR_ANY
+ USE_CLASSADDR = uses IN_CLASSx_HOST where x=A,B,C or D for broadcast
+*/
+#define BBMD_ENABLED 1
+
+/* name of file in which BDT table will be stored */
+#define BBMD_BACKUP_FILE beremiz_BACnet_BDT_table
+
+/* Enable the Gateway (Routing) functionality here, if desired. */
+#define MAX_NUM_DEVICES 1 /* Just the one normal BACnet Device Object */
+
+
+/* Define your processor architecture as
+ Big Endian (PowerPC,68K,Sparc) or Little Endian (Intel,AVR)
+ ARM and MIPS can be either - what is your setup? */
+
+/* WARNING: The following files are being included:
+ * <stdib.h> --> <endian.h> --> <bits/endian.h>
+ *
+ * endian.h defines the following constants as:
+ * #define __LITTLE_ENDIAN and LITTLE_ENDIAN as 1234
+ * #define __BIG_ENDIAN and BIG_ENDIAN as 4321
+ * #define __PDP_ENDIAN and PDP_ENDIAN as 3412
+ *
+ * bits/endian.h defines the constant BYTE_ORDER as:
+ * #define __BYTE_ORDER as __LITTLE_ENDIAN
+ *
+ * endian.h then sets the following constants
+ * (if __USE_BSD is set, which seems to be true):
+ * # define LITTLE_ENDIAN __LITTLE_ENDIAN
+ * # define BIG_ENDIAN __BIG_ENDIAN
+ * # define PDP_ENDIAN __PDP_ENDIAN
+ * # define BYTE_ORDER __BYTE_ORDER
+ *
+ * CONCLUSION:
+ * The bacnet library uses the BIG_ENDIAN constant (set to 0, or anything <>0)
+ * to indicate whether we are compiling on a little or big endian platform.
+ * However, <stdlib.h> is defining this same constant as '4321' !!!
+ * The decision to use BIG_ENDIAN as the constant is a unfortunate
+ * on the part of the bacnet coders, but we live with it for now...
+ * We simply start off by undefining the BIG_ENDIAN constant, and carry
+ * on from there!
+ */
+#undef BIG_ENDIAN
+
+#ifndef BIG_ENDIAN
+#if defined(__GNUC__)
+ /* We have GCC, which should define __LITTLE_ENDIAN__ or __BIG_ENDIAN__ */
+# if defined(__LITTLE_ENDIAN__)
+/*# warning "Using gcc to determine platform endianness."*/
+# define BIG_ENDIAN 0
+# elif defined(__BIG_ENDIAN__)
+/*# warning "Using gcc to determine platform endianness."*/
+# define BIG_ENDIAN 1
+# endif
+#endif /* __GNUC__ */
+#endif /* BIG_ENDIAN */
+
+
+/* If we still don't know byte order, try to get it from <endian.h> */
+#ifndef BIG_ENDIAN
+#include <endian.h>
+# ifdef BYTE_ORDER
+# if BYTE_ORDER == LITTLE_ENDIAN
+/*# warning "Using <endian.h> to determine platform endianness."*/
+# undef BIG_ENDIAN
+# define BIG_ENDIAN 0
+# elif BYTE_ORDER == BIG_ENDIAN
+/*# warning "Using <endian.h> to determine platform endianness."*/
+# undef BIG_ENDIAN
+# define BIG_ENDIAN 1
+# else
+# undef BIG_ENDIAN
+# endif
+# endif /* BYTE_ORDER */
+#endif /* BIG_ENDIAN */
+
+
+#ifndef BIG_ENDIAN
+#error "Unable to determine platform's byte order. Aborting compilation."
+#elif BIG_ENDIAN
+/*#warning "Compiling for BIG endian platform."*/
+#else
+/*#warning "Compiling for LITTLE endian platform."*/
+#endif
+
+
+
+/* Define your Vendor Identifier assigned by ASHRAE */
+#define BACNET_VENDOR_ID %(BACnet_Vendor_ID)s
+#define BACNET_VENDOR_NAME "%(BACnet_Vendor_Name)s"
+#define BACNET_DEVICE_MODEL_NAME "%(BACnet_Model_Name)s"
+#define BACNET_FIRMWARE_REVISION "Beremiz BACnet Extension, BACnet Stack:" BACNET_VERSION_TEXT
+#define BACNET_DEVICE_LOCATION "%(BACnet_Device_Location)s"
+#define BACNET_DEVICE_DESCRIPTION "%(BACnet_Device_Description)s"
+#define BACNET_DEVICE_APPSOFT_VER "%(BACnet_Device_AppSoft_Version)s"
+
+
+/* Max number of bytes in an APDU. */
+/* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */
+/* This is used in constructing messages and to tell others our limits */
+/* 50 is the minimum; adjust to your memory and physical layer constraints */
+/* Lon=206, MS/TP=480, ARCNET=480, Ethernet=1476, BACnet/IP=1476 */
+#define MAX_APDU 1476
+/* #define MAX_APDU 128 enable this IP for testing readrange so you get the More Follows flag set */
+
+
+/* for confirmed messages, this is the number of transactions */
+/* that we hold in a queue waiting for timeout. */
+/* Configure to zero if you don't want any confirmed messages */
+/* Configure from 1..255 for number of outstanding confirmed */
+/* requests available. */
+#define MAX_TSM_TRANSACTIONS 255
+
+/* The address cache is used for binding to BACnet devices */
+/* The number of entries corresponds to the number of */
+/* devices that might respond to an I-Am on the network. */
+/* If your device is a simple server and does not need to bind, */
+/* then you don't need to use this. */
+#define MAX_ADDRESS_CACHE 255
+
+/* some modules have debugging enabled using PRINT_ENABLED */
+#define PRINT_ENABLED 0
+
+
+/* BACAPP decodes WriteProperty service requests
+ Choose the datatypes that your application supports */
+#if !(defined(BACAPP_ALL) || \
+ defined(BACAPP_NULL) || \
+ defined(BACAPP_BOOLEAN) || \
+ defined(BACAPP_UNSIGNED) || \
+ defined(BACAPP_SIGNED) || \
+ defined(BACAPP_REAL) || \
+ defined(BACAPP_DOUBLE) || \
+ defined(BACAPP_OCTET_STRING) || \
+ defined(BACAPP_CHARACTER_STRING) || \
+ defined(BACAPP_BIT_STRING) || \
+ defined(BACAPP_ENUMERATED) || \
+ defined(BACAPP_DATE) || \
+ defined(BACAPP_TIME) || \
+ defined(BACAPP_OBJECT_ID) || \
+ defined(BACAPP_DEVICE_OBJECT_PROP_REF))
+#define BACAPP_ALL
+#endif
+
+#if defined (BACAPP_ALL)
+#define BACAPP_NULL
+#define BACAPP_BOOLEAN
+#define BACAPP_UNSIGNED
+#define BACAPP_SIGNED
+#define BACAPP_REAL
+#define BACAPP_DOUBLE
+#define BACAPP_OCTET_STRING
+#define BACAPP_CHARACTER_STRING
+#define BACAPP_BIT_STRING
+#define BACAPP_ENUMERATED
+#define BACAPP_DATE
+#define BACAPP_TIME
+#define BACAPP_OBJECT_ID
+#define BACAPP_DEVICE_OBJECT_PROP_REF
+#endif
+
+/*
+** Set the maximum vector type sizes
+*/
+#define MAX_BITSTRING_BYTES (15)
+#define MAX_CHARACTER_STRING_BYTES (MAX_APDU-6)
+#define MAX_OCTET_STRING_BYTES (MAX_APDU-6)
+
+/*
+** Control the selection of services etc to enable code size reduction for those
+** compiler suites which do not handle removing of unused functions in modules
+** so well.
+**
+** We will start with the A type services code first as these are least likely
+** to be required in embedded systems using the stack.
+*/
+
+/*
+** Define the services that may be required.
+**
+**/
+
+/* For the moment enable them all to avoid breaking things */
+#define BACNET_SVC_I_HAVE_A 1 /* Do we send I_Have requests? */
+#define BACNET_SVC_WP_A 1 /* Do we send WriteProperty requests? */
+#define BACNET_SVC_RP_A 1 /* Do we send ReadProperty requests? */
+#define BACNET_SVC_RPM_A 1 /* Do we send ReadPropertyMultiple requests? */
+#define BACNET_SVC_DCC_A 1 /* Do we send DeviceCommunicationControl requests? */
+#define BACNET_SVC_RD_A 1 /* Do we send ReinitialiseDevice requests? */
+#define BACNET_SVC_TS_A 1
+#define BACNET_SVC_SERVER 0 /* Are we a pure server type device? */
+#define BACNET_USE_OCTETSTRING 1 /* Do we need any octet strings? */
+#define BACNET_USE_DOUBLE 1 /* Do we need any doubles? */
+#define BACNET_USE_SIGNED 1 /* Do we need any signed integers */
+
+/* Do them one by one */
+#ifndef BACNET_SVC_I_HAVE_A /* Do we send I_Have requests? */
+#define BACNET_SVC_I_HAVE_A 0
+#endif
+
+#ifndef BACNET_SVC_WP_A /* Do we send WriteProperty requests? */
+#define BACNET_SVC_WP_A 0
+#endif
+
+#ifndef BACNET_SVC_RP_A /* Do we send ReadProperty requests? */
+#define BACNET_SVC_RP_A 0
+#endif
+
+#ifndef BACNET_SVC_RPM_A /* Do we send ReadPropertyMultiple requests? */
+#define BACNET_SVC_RPM_A 0
+#endif
+
+#ifndef BACNET_SVC_DCC_A /* Do we send DeviceCommunicationControl requests? */
+#define BACNET_SVC_DCC_A 0
+#endif
+
+#ifndef BACNET_SVC_RD_A /* Do we send ReinitialiseDevice requests? */
+#define BACNET_SVC_RD_A 0
+#endif
+
+#ifndef BACNET_SVC_SERVER /* Are we a pure server type device? */
+#define BACNET_SVC_SERVER 1
+#endif
+
+#ifndef BACNET_USE_OCTETSTRING /* Do we need any octet strings? */
+#define BACNET_USE_OCTETSTRING 0
+#endif
+
+#ifndef BACNET_USE_DOUBLE /* Do we need any doubles? */
+#define BACNET_USE_DOUBLE 0
+#endif
+
+#ifndef BACNET_USE_SIGNED /* Do we need any signed integers */
+#define BACNET_USE_SIGNED 0
+#endif
+
+#endif /* CONFIG_BACNET_FOR_BEREMIZ_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/device.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,1685 @@
+/**************************************************************************
+*
+* Copyright (C) 2005,2006,2009 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/** Base "class" for handling all BACnet objects belonging
+ * to a BACnet device, as well as Device-specific properties. */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h> /* for memmove */
+#include <time.h> /* for timezone, localtime */
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz plugin */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "apdu.h"
+#include "wp.h" /* WriteProperty handling */
+#include "rp.h" /* ReadProperty handling */
+#include "dcc.h" /* DeviceCommunicationControl handling */
+#include "version.h"
+#include "device_%(locstr)s.h" /* me */
+#include "handlers.h"
+#include "datalink.h"
+#include "address.h"
+/* os specfic includes */
+#include "timer.h"
+/* include the device object */
+#include "device_%(locstr)s.h"
+#include "ai_%(locstr)s.h"
+#include "ao_%(locstr)s.h"
+#include "av_%(locstr)s.h"
+#include "bi_%(locstr)s.h"
+#include "bo_%(locstr)s.h"
+#include "bv_%(locstr)s.h"
+#include "msi_%(locstr)s.h"
+#include "mso_%(locstr)s.h"
+#include "msv_%(locstr)s.h"
+
+
+#if defined(__BORLANDC__) || defined(_WIN32)
+/* Not included in time.h as specified by The Open Group */
+/* Difference from UTC and local standard time */
+long int timezone;
+#endif
+
+/* local forward (semi-private) and external prototypes */
+int Device_Read_Property_Local(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+bool Device_Write_Property_Local(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+extern int Routed_Device_Read_Property_Local(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+extern bool Routed_Device_Write_Property_Local(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+/* may be overridden by outside table */
+static object_functions_t *Object_Table;
+
+static object_functions_t My_Object_Table[] = {
+ {OBJECT_DEVICE,
+ NULL /* Init - don't init Device or it will recurse! */ ,
+ Device_Count,
+ Device_Index_To_Instance,
+ Device_Valid_Object_Instance_Number,
+ Device_Object_Name,
+ Device_Read_Property_Local,
+ Device_Write_Property_Local,
+ Device_Property_Lists,
+ DeviceGetRRInfo,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_ANALOG_INPUT,
+ Analog_Input_Init,
+ Analog_Input_Count,
+ Analog_Input_Index_To_Instance,
+ Analog_Input_Valid_Instance,
+ Analog_Input_Object_Name,
+ Analog_Input_Read_Property,
+ Analog_Input_Write_Property,
+ Analog_Input_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_ANALOG_OUTPUT,
+ Analog_Output_Init,
+ Analog_Output_Count,
+ Analog_Output_Index_To_Instance,
+ Analog_Output_Valid_Instance,
+ Analog_Output_Object_Name,
+ Analog_Output_Read_Property,
+ Analog_Output_Write_Property,
+ Analog_Output_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_ANALOG_VALUE,
+ Analog_Value_Init,
+ Analog_Value_Count,
+ Analog_Value_Index_To_Instance,
+ Analog_Value_Valid_Instance,
+ Analog_Value_Object_Name,
+ Analog_Value_Read_Property,
+ Analog_Value_Write_Property,
+ Analog_Value_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_BINARY_INPUT,
+ Binary_Input_Init,
+ Binary_Input_Count,
+ Binary_Input_Index_To_Instance,
+ Binary_Input_Valid_Instance,
+ Binary_Input_Object_Name,
+ Binary_Input_Read_Property,
+ Binary_Input_Write_Property,
+ Binary_Input_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_BINARY_OUTPUT,
+ Binary_Output_Init,
+ Binary_Output_Count,
+ Binary_Output_Index_To_Instance,
+ Binary_Output_Valid_Instance,
+ Binary_Output_Object_Name,
+ Binary_Output_Read_Property,
+ Binary_Output_Write_Property,
+ Binary_Output_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_BINARY_VALUE,
+ Binary_Value_Init,
+ Binary_Value_Count,
+ Binary_Value_Index_To_Instance,
+ Binary_Value_Valid_Instance,
+ Binary_Value_Object_Name,
+ Binary_Value_Read_Property,
+ Binary_Value_Write_Property,
+ Binary_Value_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_MULTI_STATE_INPUT,
+ Multistate_Input_Init,
+ Multistate_Input_Count,
+ Multistate_Input_Index_To_Instance,
+ Multistate_Input_Valid_Instance,
+ Multistate_Input_Object_Name,
+ Multistate_Input_Read_Property,
+ Multistate_Input_Write_Property,
+ Multistate_Input_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_MULTI_STATE_OUTPUT,
+ Multistate_Output_Init,
+ Multistate_Output_Count,
+ Multistate_Output_Index_To_Instance,
+ Multistate_Output_Valid_Instance,
+ Multistate_Output_Object_Name,
+ Multistate_Output_Read_Property,
+ Multistate_Output_Write_Property,
+ Multistate_Output_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {OBJECT_MULTI_STATE_VALUE,
+ Multistate_Value_Init,
+ Multistate_Value_Count,
+ Multistate_Value_Index_To_Instance,
+ Multistate_Value_Valid_Instance,
+ Multistate_Value_Object_Name,
+ Multistate_Value_Read_Property,
+ Multistate_Value_Write_Property,
+ Multistate_Value_Property_Lists,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ },
+ {MAX_BACNET_OBJECT_TYPE,
+ NULL /* Init */ ,
+ NULL /* Count */ ,
+ NULL /* Index_To_Instance */ ,
+ NULL /* Valid_Instance */ ,
+ NULL /* Object_Name */ ,
+ NULL /* Read_Property */ ,
+ NULL /* Write_Property */ ,
+ NULL /* Property_Lists */ ,
+ NULL /* ReadRangeInfo */ ,
+ NULL /* Iterator */ ,
+ NULL /* Value_Lists */ ,
+ NULL /* COV */ ,
+ NULL /* COV Clear */ ,
+ NULL /* Intrinsic Reporting */ }
+};
+
+/** Glue function to let the Device object, when called by a handler,
+ * lookup which Object type needs to be invoked.
+ * param: Object_Type [in] The type of BACnet Object the handler wants to access.
+ * return: Pointer to the group of object helper functions that implement this
+ * type of Object.
+ */
+static struct object_functions *Device_Objects_Find_Functions(
+ BACNET_OBJECT_TYPE Object_Type)
+{
+ struct object_functions *pObject = NULL;
+
+ pObject = Object_Table;
+ while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+ /* handle each object type */
+ if (pObject->Object_Type == Object_Type) {
+ return (pObject);
+ }
+ pObject++;
+ }
+
+ return (NULL);
+}
+
+/** Try to find a rr_info_function helper function for the requested object type.
+ *
+ * param: object_type [in] The type of BACnet Object the handler wants to access.
+ * return: Pointer to the object helper function that implements the
+ * ReadRangeInfo function, Object_RR_Info, for this type of Object on
+ * success, else a NULL pointer if the type of Object isn't supported
+ * or doesn't have a ReadRangeInfo function.
+ */
+rr_info_function Device_Objects_RR_Info(
+ BACNET_OBJECT_TYPE object_type)
+{
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ return (pObject != NULL ? pObject->Object_RR_Info : NULL);
+}
+
+/** For a given object type, returns the special property list.
+ * This function is used for ReadPropertyMultiple calls which want
+ * just Required, just Optional, or All properties.
+ *
+ * param: object_type [in] The desired BACNET_OBJECT_TYPE whose properties
+ * are to be listed.
+ * param: pPropertyList [out] Reference to the structure which will, on return,
+ * list, separately, the Required, Optional, and Proprietary object
+ * properties with their counts.
+ */
+void Device_Objects_Property_List(
+ BACNET_OBJECT_TYPE object_type,
+ struct special_property_list_t *pPropertyList)
+{
+ struct object_functions *pObject = NULL;
+
+ pPropertyList->Required.pList = NULL;
+ pPropertyList->Optional.pList = NULL;
+ pPropertyList->Proprietary.pList = NULL;
+
+ /* If we can find an entry for the required object type
+ * and there is an Object_List_RPM fn ptr then call it
+ * to populate the pointers to the individual list counters.
+ */
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) {
+ pObject->Object_RPM_List(&pPropertyList->Required.pList,
+ &pPropertyList->Optional.pList, &pPropertyList->Proprietary.pList);
+ }
+
+ /* Fetch the counts if available otherwise zero them */
+ pPropertyList->Required.count =
+ pPropertyList->Required.pList ==
+ NULL ? 0 : property_list_count(pPropertyList->Required.pList);
+
+ pPropertyList->Optional.count =
+ pPropertyList->Optional.pList ==
+ NULL ? 0 : property_list_count(pPropertyList->Optional.pList);
+
+ pPropertyList->Proprietary.count =
+ pPropertyList->Proprietary.pList ==
+ NULL ? 0 : property_list_count(pPropertyList->Proprietary.pList);
+
+ return;
+}
+
+/** Commands a Device re-initialization, to a given state.
+ * The request's password must match for the operation to succeed.
+ * This implementation provides a framework, but doesn't
+ * actually *DO* anything.
+ * @note You could use a mix of states and passwords to multiple outcomes.
+ * @note You probably want to restart *after* the simple ack has been sent
+ * from the return handler, so just set a local flag here.
+ *
+ * param: rd_data [in,out] The information from the RD request.
+ * On failure, the error class and code will be set.
+ * return: True if succeeds (password is correct), else False.
+ */
+bool Device_Reinitialize(
+ BACNET_REINITIALIZE_DEVICE_DATA * rd_data)
+{
+ bool status = false;
+
+ if (characterstring_ansi_same(&rd_data->password, "Jesus")) {
+ switch (rd_data->state) {
+ case BACNET_REINIT_COLDSTART:
+ case BACNET_REINIT_WARMSTART:
+ dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
+ break;
+ case BACNET_REINIT_STARTBACKUP:
+ break;
+ case BACNET_REINIT_ENDBACKUP:
+ break;
+ case BACNET_REINIT_STARTRESTORE:
+ break;
+ case BACNET_REINIT_ENDRESTORE:
+ break;
+ case BACNET_REINIT_ABORTRESTORE:
+ break;
+ default:
+ break;
+ }
+ /* Note: you could use a mix of state
+ and password to multiple things */
+ /* note: you probably want to restart *after* the
+ simple ack has been sent from the return handler
+ so just set a flag from here */
+ status = true;
+ } else {
+ rd_data->error_class = ERROR_CLASS_SECURITY;
+ rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE;
+ }
+
+ return status;
+}
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Device_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* W R ( 75) */
+ PROP_OBJECT_NAME, /* W R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_SYSTEM_STATUS, /* R R (112) */
+ PROP_VENDOR_NAME, /* R R (121) */
+ PROP_VENDOR_IDENTIFIER, /* W R (120) */
+ PROP_MODEL_NAME, /* W R ( 70) */
+ PROP_FIRMWARE_REVISION, /* R R ( 44) */
+ PROP_APPLICATION_SOFTWARE_VERSION, /* R R ( 12) */
+ PROP_PROTOCOL_VERSION, /* R R ( 98) */
+ PROP_PROTOCOL_REVISION, /* R R (139) */
+ PROP_PROTOCOL_SERVICES_SUPPORTED, /* R R ( 97) */
+ PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, /* R R ( 96) */
+ PROP_OBJECT_LIST, /* R R ( 76) */
+ PROP_MAX_APDU_LENGTH_ACCEPTED, /* R R ( 62) */
+ PROP_SEGMENTATION_SUPPORTED, /* R R (107) */
+ PROP_APDU_TIMEOUT, /* W R ( 11) */
+ PROP_NUMBER_OF_APDU_RETRIES, /* W R ( 73) */
+ PROP_DEVICE_ADDRESS_BINDING, /* R R ( 30) */
+ PROP_DATABASE_REVISION, /* R R (155) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Device_Properties_Optional[] = {
+ PROP_DESCRIPTION, /* W O ( 28) */
+ PROP_LOCAL_TIME, /* R O ( 57) */
+ PROP_UTC_OFFSET, /* R O (119) */
+ PROP_LOCAL_DATE, /* R O ( 56) */
+ PROP_DAYLIGHT_SAVINGS_STATUS, /* R O ( 24) */
+ PROP_LOCATION, /* W O ( 58) */
+ -1
+};
+
+static const int Device_Properties_Proprietary[] = {
+ -1
+};
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Binary_Value_Init() based off the values
+ * stored in Binary_Value_Properties_Required
+ * Binary_Value_Properties_Optional
+ * Binary_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Device_Properties_List[64];
+
+
+void Device_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Device_Properties_Required;
+ if (pOptional)
+ *pOptional = Device_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Device_Properties_Proprietary;
+
+ return;
+}
+
+
+static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
+
+static BACNET_CHARACTER_STRING My_Object_Name;
+static uint32_t Object_Instance_Number = 260001;
+static uint16_t Vendor_Identifier = BACNET_VENDOR_ID;
+static char *Vendor_Name = BACNET_VENDOR_NAME;
+static char *Firmware_Revision = BACNET_FIRMWARE_REVISION;
+static char Model_Name [MAX_DEV_MOD_LEN + 1] = BACNET_DEVICE_MODEL_NAME;
+static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = BACNET_DEVICE_APPSOFT_VER;
+static char Location [MAX_DEV_LOC_LEN + 1] = BACNET_DEVICE_LOCATION;
+static char Description [MAX_DEV_DESC_LEN + 1] = BACNET_DEVICE_DESCRIPTION;
+/* static uint8_t Protocol_Version = 1; - constant, not settable */
+/* static uint8_t Protocol_Revision = 4; - constant, not settable */
+/* Protocol_Services_Supported - dynamically generated */
+/* Protocol_Object_Types_Supported - in RP encoding */
+/* Object_List - dynamically generated */
+/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */
+/* static uint8_t Max_Segments_Accepted = 0; */
+/* VT_Classes_Supported */
+/* Active_VT_Sessions */
+static BACNET_TIME Local_Time; /* rely on OS, if there is one */
+static BACNET_DATE Local_Date; /* rely on OS, if there is one */
+/* NOTE: BACnet UTC Offset is inverse of common practice.
+ If your UTC offset is -5hours of GMT,
+ then BACnet UTC offset is +5hours.
+ BACnet UTC offset is expressed in minutes. */
+static int32_t UTC_Offset = 5 * 60;
+static bool Daylight_Savings_Status = false; /* rely on OS */
+/* List_Of_Session_Keys */
+/* Time_Synchronization_Recipients */
+/* Max_Master - rely on MS/TP subsystem, if there is one */
+/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */
+/* Device_Address_Binding - required, but relies on binding cache */
+static uint32_t Database_Revision = 0;
+/* Configuration_Files */
+/* Last_Restore_Time */
+/* Backup_Failure_Timeout */
+/* Active_COV_Subscriptions */
+/* Slave_Proxy_Enable */
+/* Manual_Slave_Address_Binding */
+/* Auto_Slave_Discovery */
+/* Slave_Address_Binding */
+/* Profile_Name */
+
+unsigned Device_Count(
+ void)
+{
+ return 1;
+}
+
+uint32_t Device_Index_To_Instance(
+ unsigned index)
+{
+ index = index;
+ return Object_Instance_Number;
+}
+
+/* methods to manipulate the data */
+
+/** Return the Object Instance number for our (single) Device Object.
+ * This is a key function, widely invoked by the handler code, since
+ * it provides "our" (ie, local) address.
+ * return: The Instance number used in the BACNET_OBJECT_ID for the Device.
+ */
+uint32_t Device_Object_Instance_Number(
+ void)
+{
+ return Object_Instance_Number;
+}
+
+bool Device_Set_Object_Instance_Number(
+ uint32_t object_id)
+{
+ bool status = true; /* return value */
+
+ if (object_id <= BACNET_MAX_INSTANCE) {
+ /* Make the change and update the database revision */
+ Object_Instance_Number = object_id;
+ Device_Inc_Database_Revision();
+ } else
+ status = false;
+
+ return status;
+}
+
+bool Device_Valid_Object_Instance_Number(
+ uint32_t object_id)
+{
+ return (Object_Instance_Number == object_id);
+}
+
+bool Device_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false;
+
+ if (object_instance == Object_Instance_Number) {
+ status = characterstring_copy(object_name, &My_Object_Name);
+ }
+
+ return status;
+}
+
+bool Device_Set_Object_Name(
+ BACNET_CHARACTER_STRING * object_name)
+{
+ bool status = false; /*return value */
+
+ if (!characterstring_same(&My_Object_Name, object_name)) {
+ /* Make the change and update the database revision */
+ status = characterstring_copy(&My_Object_Name, object_name);
+ Device_Inc_Database_Revision();
+ }
+
+ return status;
+}
+
+BACNET_DEVICE_STATUS Device_System_Status(
+ void)
+{
+ return System_Status;
+}
+
+int Device_Set_System_Status(
+ BACNET_DEVICE_STATUS status,
+ bool local)
+{
+ int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */
+
+ /* We limit the options available depending on whether the source is
+ * internal or external. */
+ if (local) {
+ switch (status) {
+ case STATUS_OPERATIONAL:
+ case STATUS_OPERATIONAL_READ_ONLY:
+ case STATUS_DOWNLOAD_REQUIRED:
+ case STATUS_DOWNLOAD_IN_PROGRESS:
+ case STATUS_NON_OPERATIONAL:
+ System_Status = status;
+ break;
+
+ /* Don't support backup at present so don't allow setting */
+ case STATUS_BACKUP_IN_PROGRESS:
+ result = -2;
+ break;
+
+ default:
+ result = -1;
+ break;
+ }
+ } else {
+ switch (status) {
+ /* Allow these for the moment as a way to easily alter
+ * overall device operation. The lack of password protection
+ * or other authentication makes allowing writes to this
+ * property a risky facility to provide.
+ */
+ case STATUS_OPERATIONAL:
+ case STATUS_OPERATIONAL_READ_ONLY:
+ case STATUS_NON_OPERATIONAL:
+ System_Status = status;
+ break;
+
+ /* Don't allow outsider set this - it should probably
+ * be set if the device config is incomplete or
+ * corrupted or perhaps after some sort of operator
+ * wipe operation.
+ */
+ case STATUS_DOWNLOAD_REQUIRED:
+ /* Don't allow outsider set this - it should be set
+ * internally at the start of a multi packet download
+ * perhaps indirectly via PT or WF to a config file.
+ */
+ case STATUS_DOWNLOAD_IN_PROGRESS:
+ /* Don't support backup at present so don't allow setting */
+ case STATUS_BACKUP_IN_PROGRESS:
+ result = -2;
+ break;
+
+ default:
+ result = -1;
+ break;
+ }
+ }
+
+ return (result);
+}
+
+const char *Device_Vendor_Name(
+ void)
+{
+ return Vendor_Name;
+}
+
+/** Returns the Vendor ID for this Device.
+ * See the assignments at http://www.bacnet.org/VendorID/BACnet%%20Vendor%%20IDs.htm
+ * return: The Vendor ID of this Device.
+ */
+uint16_t Device_Vendor_Identifier(
+ void)
+{
+ return Vendor_Identifier;
+}
+
+void Device_Set_Vendor_Identifier(
+ uint16_t vendor_id)
+{
+ Vendor_Identifier = vendor_id;
+}
+
+const char *Device_Model_Name(
+ void)
+{
+ return Model_Name;
+}
+
+bool Device_Set_Model_Name(
+ const char *name,
+ size_t length)
+{
+ bool status = false; /*return value */
+
+ if (length < sizeof(Model_Name)) {
+ memmove(Model_Name, name, length);
+ Model_Name[length] = 0;
+ status = true;
+ }
+
+ return status;
+}
+
+const char *Device_Firmware_Revision(
+ void)
+{
+ return Firmware_Revision;
+}
+
+const char *Device_Application_Software_Version(
+ void)
+{
+ return Application_Software_Version;
+}
+
+bool Device_Set_Application_Software_Version(
+ const char *name,
+ size_t length)
+{
+ bool status = false; /*return value */
+
+ if (length < sizeof(Application_Software_Version)) {
+ memmove(Application_Software_Version, name, length);
+ Application_Software_Version[length] = 0;
+ status = true;
+ }
+
+ return status;
+}
+
+const char *Device_Description(
+ void)
+{
+ return Description;
+}
+
+bool Device_Set_Description(
+ const char *name,
+ size_t length)
+{
+ bool status = false; /*return value */
+
+ if (length < sizeof(Description)) {
+ memmove(Description, name, length);
+ Description[length] = 0;
+ status = true;
+ }
+
+ return status;
+}
+
+const char *Device_Location(
+ void)
+{
+ return Location;
+}
+
+bool Device_Set_Location(
+ const char *name,
+ size_t length)
+{
+ bool status = false; /*return value */
+
+ if (length < sizeof(Location)) {
+ memmove(Location, name, length);
+ Location[length] = 0;
+ status = true;
+ }
+
+ return status;
+}
+
+uint8_t Device_Protocol_Version(
+ void)
+{
+ return BACNET_PROTOCOL_VERSION;
+}
+
+uint8_t Device_Protocol_Revision(
+ void)
+{
+ return BACNET_PROTOCOL_REVISION;
+}
+
+BACNET_SEGMENTATION Device_Segmentation_Supported(
+ void)
+{
+ return SEGMENTATION_NONE;
+}
+
+uint32_t Device_Database_Revision(
+ void)
+{
+ return Database_Revision;
+}
+
+void Device_Set_Database_Revision(
+ uint32_t revision)
+{
+ Database_Revision = revision;
+}
+
+/*
+ * Shortcut for incrementing database revision as this is potentially
+ * the most common operation if changing object names and ids is
+ * implemented.
+ */
+void Device_Inc_Database_Revision(
+ void)
+{
+ Database_Revision++;
+}
+
+/** Get the total count of objects supported by this Device Object.
+ * @note Since many network clients depend on the object list
+ * for discovery, it must be consistent!
+ * return: The count of objects, for all supported Object types.
+ */
+unsigned Device_Object_List_Count(
+ void)
+{
+ unsigned count = 0; /* number of objects */
+ struct object_functions *pObject = NULL;
+
+ /* initialize the default return values */
+ pObject = Object_Table;
+ while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+ if (pObject->Object_Count) {
+ count += pObject->Object_Count();
+ }
+ pObject++;
+ }
+
+ return count;
+}
+
+/** Lookup the Object at the given array index in the Device's Object List.
+ * Even though we don't keep a single linear array of objects in the Device,
+ * this method acts as though we do and works through a virtual, concatenated
+ * array of all of our object type arrays.
+ *
+ * param: array_index [in] The desired array index (1 to N)
+ * param: object_type [out] The object's type, if found.
+ * param: instance [out] The object's instance number, if found.
+ * return: True if found, else false.
+ */
+bool Device_Object_List_Identifier(
+ unsigned array_index,
+ int *object_type,
+ uint32_t * instance)
+{
+ bool status = false;
+ unsigned count = 0;
+ unsigned object_index = 0;
+ unsigned temp_index = 0;
+ struct object_functions *pObject = NULL;
+
+ /* array index zero is length - so invalid */
+ if (array_index == 0) {
+ return status;
+ }
+ object_index = array_index - 1;
+ /* initialize the default return values */
+ pObject = Object_Table;
+ while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+ if (pObject->Object_Count) {
+ object_index -= count;
+ count = pObject->Object_Count();
+ if (object_index < count) {
+ /* Use the iterator function if available otherwise
+ * look for the index to instance to get the ID */
+ if (pObject->Object_Iterator) {
+ /* First find the first object */
+ temp_index = pObject->Object_Iterator(~(unsigned) 0);
+ /* Then step through the objects to find the nth */
+ while (object_index != 0) {
+ temp_index = pObject->Object_Iterator(temp_index);
+ object_index--;
+ }
+ /* set the object_index up before falling through to next bit */
+ object_index = temp_index;
+ }
+ if (pObject->Object_Index_To_Instance) {
+ *object_type = pObject->Object_Type;
+ *instance =
+ pObject->Object_Index_To_Instance(object_index);
+ status = true;
+ break;
+ }
+ }
+ }
+ pObject++;
+ }
+
+ return status;
+}
+
+/** Determine if we have an object with the given object_name.
+ * If the object_type and object_instance pointers are not null,
+ * and the lookup succeeds, they will be given the resulting values.
+ * param: object_name [in] The desired Object Name to look for.
+ * param: object_type [out] The BACNET_OBJECT_TYPE of the matching Object.
+ * param: object_instance [out] The object instance number of the matching Object.
+ * return: True on success or else False if not found.
+ */
+bool Device_Valid_Object_Name(
+ BACNET_CHARACTER_STRING * object_name1,
+ int *object_type,
+ uint32_t * object_instance)
+{
+ bool found = false;
+ int type = 0;
+ uint32_t instance;
+ unsigned max_objects = 0, i = 0;
+ bool check_id = false;
+ BACNET_CHARACTER_STRING object_name2;
+ struct object_functions *pObject = NULL;
+
+ max_objects = Device_Object_List_Count();
+ for (i = 1; i <= max_objects; i++) {
+ check_id = Device_Object_List_Identifier(i, &type, &instance);
+ if (check_id) {
+ pObject = Device_Objects_Find_Functions(type);
+ if ((pObject != NULL) && (pObject->Object_Name != NULL) &&
+ (pObject->Object_Name(instance, &object_name2) &&
+ characterstring_same(object_name1, &object_name2))) {
+ found = true;
+ if (object_type) {
+ *object_type = type;
+ }
+ if (object_instance) {
+ *object_instance = instance;
+ }
+ break;
+ }
+ }
+ }
+
+ return found;
+}
+
+/** Determine if we have an object of this type and instance number.
+ * param: object_type [in] The desired BACNET_OBJECT_TYPE
+ * param: object_instance [in] The object instance number to be looked up.
+ * return: True if found, else False if no such Object in this device.
+ */
+bool Device_Valid_Object_Id(
+ int object_type,
+ uint32_t object_instance)
+{
+ bool status = false; /* return value */
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) {
+ status = pObject->Object_Valid_Instance(object_instance);
+ }
+
+ return status;
+}
+
+/** Copy a child object's object_name value, given its ID.
+ * param: object_type [in] The BACNET_OBJECT_TYPE of the child Object.
+ * param: object_instance [in] The object instance number of the child Object.
+ * param: object_name [out] The Object Name found for this child Object.
+ * return: True on success or else False if not found.
+ */
+bool Device_Object_Name_Copy(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ struct object_functions *pObject = NULL;
+ bool found = false;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if ((pObject != NULL) && (pObject->Object_Name != NULL)) {
+ found = pObject->Object_Name(object_instance, object_name);
+ }
+
+ return found;
+}
+
+static void Device_Update_Current_Time(
+ void)
+{
+ struct tm *tblock = NULL;
+#if defined(_MSC_VER)
+ time_t tTemp;
+#else
+ struct timeval tv;
+#endif
+/*
+struct tm
+
+int tm_sec Seconds [0,60].
+int tm_min Minutes [0,59].
+int tm_hour Hour [0,23].
+int tm_mday Day of month [1,31].
+int tm_mon Month of year [0,11].
+int tm_year Years since 1900.
+int tm_wday Day of week [0,6] (Sunday =0).
+int tm_yday Day of year [0,365].
+int tm_isdst Daylight Savings flag.
+*/
+#if defined(_MSC_VER)
+ time(&tTemp);
+ tblock = localtime(&tTemp);
+#else
+ if (gettimeofday(&tv, NULL) == 0) {
+ tblock = localtime(&tv.tv_sec);
+ }
+#endif
+
+ if (tblock) {
+ datetime_set_date(&Local_Date, (uint16_t) tblock->tm_year + 1900,
+ (uint8_t) tblock->tm_mon + 1, (uint8_t) tblock->tm_mday);
+#if !defined(_MSC_VER)
+ datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour,
+ (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec,
+ (uint8_t) (tv.tv_usec / 10000));
+#else
+ datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour,
+ (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, 0);
+#endif
+ if (tblock->tm_isdst) {
+ Daylight_Savings_Status = true;
+ } else {
+ Daylight_Savings_Status = false;
+ }
+ /* note: timezone is declared in <time.h> stdlib. */
+ UTC_Offset = timezone / 60;
+ } else {
+ datetime_date_wildcard_set(&Local_Date);
+ datetime_time_wildcard_set(&Local_Time);
+ Daylight_Savings_Status = false;
+ }
+}
+
+void Device_getCurrentDateTime(
+ BACNET_DATE_TIME * DateTime)
+{
+ Device_Update_Current_Time();
+
+ DateTime->date = Local_Date;
+ DateTime->time = Local_Time;
+}
+
+int32_t Device_UTC_Offset(void)
+{
+ Device_Update_Current_Time();
+
+ return UTC_Offset;
+}
+
+bool Device_Daylight_Savings_Status(void)
+{
+ return Daylight_Savings_Status;
+}
+
+/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
+ BACNET_STATUS_ABORT for abort message */
+int Device_Read_Property_Local(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int apdu_len = 0; /* return value */
+ int len = 0; /* apdu len intermediate value */
+ BACNET_BIT_STRING bit_string = { 0 };
+ BACNET_CHARACTER_STRING char_string = { 0 };
+ unsigned i = 0;
+ int object_type = 0;
+ uint32_t instance = 0;
+ unsigned count = 0;
+ uint8_t *apdu = NULL;
+ struct object_functions *pObject = NULL;
+ bool found = false;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0], OBJECT_DEVICE,
+ Object_Instance_Number);
+ break;
+ case PROP_OBJECT_NAME:
+ apdu_len =
+ encode_application_character_string(&apdu[0], &My_Object_Name);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE);
+ break;
+ case PROP_DESCRIPTION:
+ characterstring_init_ansi(&char_string, Description);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_SYSTEM_STATUS:
+ apdu_len = encode_application_enumerated(&apdu[0], System_Status);
+ break;
+ case PROP_VENDOR_NAME:
+ characterstring_init_ansi(&char_string, Vendor_Name);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_VENDOR_IDENTIFIER:
+ apdu_len =
+ encode_application_unsigned(&apdu[0], Vendor_Identifier);
+ break;
+ case PROP_MODEL_NAME:
+ characterstring_init_ansi(&char_string, Model_Name);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_FIRMWARE_REVISION:
+ characterstring_init_ansi(&char_string, Firmware_Revision);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_APPLICATION_SOFTWARE_VERSION:
+ characterstring_init_ansi(&char_string,
+ Application_Software_Version);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_LOCATION:
+ characterstring_init_ansi(&char_string, Location);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_LOCAL_TIME:
+ Device_Update_Current_Time();
+ apdu_len = encode_application_time(&apdu[0], &Local_Time);
+ break;
+ case PROP_UTC_OFFSET:
+ Device_Update_Current_Time();
+ apdu_len = encode_application_signed(&apdu[0], UTC_Offset);
+ break;
+ case PROP_LOCAL_DATE:
+ Device_Update_Current_Time();
+ apdu_len = encode_application_date(&apdu[0], &Local_Date);
+ break;
+ case PROP_DAYLIGHT_SAVINGS_STATUS:
+ Device_Update_Current_Time();
+ apdu_len =
+ encode_application_boolean(&apdu[0], Daylight_Savings_Status);
+ break;
+ case PROP_PROTOCOL_VERSION:
+ apdu_len =
+ encode_application_unsigned(&apdu[0],
+ Device_Protocol_Version());
+ break;
+ case PROP_PROTOCOL_REVISION:
+ apdu_len =
+ encode_application_unsigned(&apdu[0],
+ Device_Protocol_Revision());
+ break;
+ case PROP_PROTOCOL_SERVICES_SUPPORTED:
+ /* Note: list of services that are executed, not initiated. */
+ bitstring_init(&bit_string);
+ for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) {
+ /* automatic lookup based on handlers set */
+ bitstring_set_bit(&bit_string, (uint8_t) i,
+ apdu_service_supported((BACNET_SERVICES_SUPPORTED) i));
+ }
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
+ /* Note: this is the list of objects that can be in this device,
+ not a list of objects that this device can access */
+ bitstring_init(&bit_string);
+ for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) {
+ /* initialize all the object types to not-supported */
+ bitstring_set_bit(&bit_string, (uint8_t) i, false);
+ }
+ /* set the object types with objects to supported */
+
+ pObject = Object_Table;
+ while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+ if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) {
+ bitstring_set_bit(&bit_string, pObject->Object_Type, true);
+ }
+ pObject++;
+ }
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_OBJECT_LIST:
+ count = Device_Object_List_Count();
+ /* Array element zero is the number of objects in the list */
+ if (rpdata->array_index == 0)
+ apdu_len = encode_application_unsigned(&apdu[0], count);
+ /* if no index was specified, then try to encode the entire list */
+ /* into one packet. Note that more than likely you will have */
+ /* to return an error if the number of encoded objects exceeds */
+ /* your maximum APDU size. */
+ else if (rpdata->array_index == BACNET_ARRAY_ALL) {
+ for (i = 1; i <= count; i++) {
+ found =
+ Device_Object_List_Identifier(i, &object_type,
+ &instance);
+ if (found) {
+ len =
+ encode_application_object_id(&apdu[apdu_len],
+ object_type, instance);
+ apdu_len += len;
+ /* assume next one is the same size as this one */
+ /* can we all fit into the APDU? Don't check for last entry */
+ if ((i != count) && (apdu_len + len) >= MAX_APDU) {
+ /* Abort response */
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ apdu_len = BACNET_STATUS_ABORT;
+ break;
+ }
+ } else {
+ /* error: internal error? */
+ rpdata->error_class = ERROR_CLASS_SERVICES;
+ rpdata->error_code = ERROR_CODE_OTHER;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ }
+ } else {
+ found =
+ Device_Object_List_Identifier(rpdata->array_index,
+ &object_type, &instance);
+ if (found) {
+ apdu_len =
+ encode_application_object_id(&apdu[0], object_type,
+ instance);
+ } else {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+ }
+ break;
+ case PROP_MAX_APDU_LENGTH_ACCEPTED:
+ apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU);
+ break;
+ case PROP_SEGMENTATION_SUPPORTED:
+ apdu_len =
+ encode_application_enumerated(&apdu[0],
+ Device_Segmentation_Supported());
+ break;
+ case PROP_APDU_TIMEOUT:
+ apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout());
+ break;
+ case PROP_NUMBER_OF_APDU_RETRIES:
+ apdu_len = encode_application_unsigned(&apdu[0], apdu_retries());
+ break;
+ case PROP_DEVICE_ADDRESS_BINDING:
+ /* FIXME: the real max apdu remaining should be passed into function */
+ apdu_len = address_list_encode(&apdu[0], MAX_APDU);
+ break;
+ case PROP_DATABASE_REVISION:
+ apdu_len =
+ encode_application_unsigned(&apdu[0], Database_Revision);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Device_Properties_List,
+// property_list_count(Device_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+/** Looks up the requested Object and Property, and encodes its Value in an APDU.
+ * If the Object or Property can't be found, sets the error class and code.
+ *
+ * param: rpdata [in,out] Structure with the desired Object and Property info
+ * on entry, and APDU message on return.
+ * return: The length of the APDU on success, else BACNET_STATUS_ERROR
+ */
+int Device_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int apdu_len = BACNET_STATUS_ERROR;
+ struct object_functions *pObject = NULL;
+
+ /* initialize the default return values */
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ pObject = Device_Objects_Find_Functions(rpdata->object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Valid_Instance &&
+ pObject->Object_Valid_Instance(rpdata->object_instance)) {
+ if (pObject->Object_Read_Property) {
+ apdu_len = pObject->Object_Read_Property(rpdata);
+ }
+ }
+ }
+
+ return apdu_len;
+}
+
+/* returns true if successful */
+bool Device_Write_Property_Local(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+ int object_type = 0;
+ uint32_t object_instance = 0;
+ int temp;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_OBJECT_LIST) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* FIXME: len < application_data_len: more data? */
+ switch (wp_data->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_OBJECT_ID,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ if ((value.type.Object_Id.type == OBJECT_DEVICE) &&
+ (Device_Set_Object_Instance_Number(value.type.
+ Object_Id.instance))) {
+ /* FIXME: we could send an I-Am broadcast to let the world know */
+ } else {
+ status = false;
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_NUMBER_OF_APDU_RETRIES:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ /* FIXME: bounds check? */
+ apdu_retries_set((uint8_t) value.type.Unsigned_Int);
+ }
+ break;
+ case PROP_APDU_TIMEOUT:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ /* FIXME: bounds check? */
+ apdu_timeout_set((uint16_t) value.type.Unsigned_Int);
+ }
+ break;
+ case PROP_VENDOR_IDENTIFIER:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ /* FIXME: bounds check? */
+ Device_Set_Vendor_Identifier((uint16_t) value.
+ type.Unsigned_Int);
+ }
+ break;
+// case PROP_SYSTEM_STATUS:
+// status =
+// WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED,
+// &wp_data->error_class, &wp_data->error_code);
+// if (status) {
+// temp = Device_Set_System_Status((BACNET_DEVICE_STATUS)
+// value.type.Enumerated, false);
+// if (temp != 0) {
+// status = false;
+// wp_data->error_class = ERROR_CLASS_PROPERTY;
+// if (temp == -1) {
+// wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+// } else {
+// wp_data->error_code =
+// ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
+// }
+// }
+// }
+// break;
+ case PROP_OBJECT_NAME:
+ status =
+ WPValidateString(&value,
+ characterstring_capacity(&My_Object_Name), false,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ /* All the object names in a device must be unique */
+ if (Device_Valid_Object_Name(&value.type.Character_String,
+ &object_type, &object_instance)) {
+ if ((object_type == wp_data->object_type) &&
+ (object_instance == wp_data->object_instance)) {
+ /* writing same name to same object */
+ status = true;
+ } else {
+ status = false;
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_DUPLICATE_NAME;
+ }
+ } else {
+ Device_Set_Object_Name(&value.type.Character_String);
+ }
+ }
+ break;
+ case PROP_LOCATION:
+ status =
+ WPValidateString(&value, MAX_DEV_LOC_LEN, true,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ Device_Set_Location(characterstring_value(&value.
+ type.Character_String),
+ characterstring_length(&value.type.Character_String));
+ }
+ break;
+
+ case PROP_DESCRIPTION:
+ status =
+ WPValidateString(&value, MAX_DEV_DESC_LEN, true,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ Device_Set_Description(characterstring_value(&value.
+ type.Character_String),
+ characterstring_length(&value.type.Character_String));
+ }
+ break;
+ case PROP_MODEL_NAME:
+ status =
+ WPValidateString(&value, MAX_DEV_MOD_LEN, true,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ Device_Set_Model_Name(characterstring_value(&value.
+ type.Character_String),
+ characterstring_length(&value.type.Character_String));
+ }
+ break;
+
+ case PROP_OBJECT_TYPE:
+ case PROP_SYSTEM_STATUS:
+ case PROP_VENDOR_NAME:
+ case PROP_FIRMWARE_REVISION:
+ case PROP_APPLICATION_SOFTWARE_VERSION:
+ case PROP_LOCAL_TIME:
+ case PROP_UTC_OFFSET:
+ case PROP_LOCAL_DATE:
+ case PROP_DAYLIGHT_SAVINGS_STATUS:
+ case PROP_PROTOCOL_VERSION:
+ case PROP_PROTOCOL_REVISION:
+ case PROP_PROTOCOL_SERVICES_SUPPORTED:
+ case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
+ case PROP_OBJECT_LIST:
+ case PROP_MAX_APDU_LENGTH_ACCEPTED:
+ case PROP_SEGMENTATION_SUPPORTED:
+ case PROP_DEVICE_ADDRESS_BINDING:
+ case PROP_DATABASE_REVISION:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+/** Looks up the requested Object and Property, and set the new Value in it,
+ * if allowed.
+ * If the Object or Property can't be found, sets the error class and code.
+ *
+ * param: wp_data [in,out] Structure with the desired Object and Property info
+ * and new Value on entry, and APDU message on return.
+ * return: True on success, else False if there is an error.
+ */
+bool Device_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* Ever the pessamist! */
+ struct object_functions *pObject = NULL;
+
+ /* initialize the default return values */
+ wp_data->error_class = ERROR_CLASS_OBJECT;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ pObject = Device_Objects_Find_Functions(wp_data->object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Valid_Instance &&
+ pObject->Object_Valid_Instance(wp_data->object_instance)) {
+ if (pObject->Object_Write_Property) {
+ status = pObject->Object_Write_Property(wp_data);
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ }
+ } else {
+ wp_data->error_class = ERROR_CLASS_OBJECT;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ }
+ } else {
+ wp_data->error_class = ERROR_CLASS_OBJECT;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ }
+
+ return (status);
+}
+
+/** Looks up the requested Object, and fills the Property Value list.
+ * If the Object or Property can't be found, returns false.
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance number to be looked up.
+ * param: [out] The value list
+ * return: True if the object instance supports this feature and value changed.
+ */
+bool Device_Encode_Value_List(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance,
+ BACNET_PROPERTY_VALUE * value_list)
+{
+ bool status = false; /* Ever the pessamist! */
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Valid_Instance &&
+ pObject->Object_Valid_Instance(object_instance)) {
+ if (pObject->Object_Value_List) {
+ status =
+ pObject->Object_Value_List(object_instance, value_list);
+ }
+ }
+ }
+
+ return (status);
+}
+
+/** Checks the COV flag in the requested Object
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance to be looked up.
+ * return: True if the COV flag is set
+ */
+bool Device_COV(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance)
+{
+ bool status = false; /* Ever the pessamist! */
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Valid_Instance &&
+ pObject->Object_Valid_Instance(object_instance)) {
+ if (pObject->Object_COV) {
+ status = pObject->Object_COV(object_instance);
+ }
+ }
+ }
+
+ return (status);
+}
+
+/** Clears the COV flag in the requested Object
+ * param: [in] The object type to be looked up.
+ * param: [in] The object instance to be looked up.
+ */
+void Device_COV_Clear(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance)
+{
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Valid_Instance &&
+ pObject->Object_Valid_Instance(object_instance)) {
+ if (pObject->Object_COV_Clear) {
+ pObject->Object_COV_Clear(object_instance);
+ }
+ }
+ }
+}
+
+
+/** Looks up the requested Object to see if the functionality is supported.
+ * param: [in] The object type to be looked up.
+ * return: True if the object instance supports this feature.
+ */
+bool Device_Value_List_Supported(
+ BACNET_OBJECT_TYPE object_type)
+{
+ bool status = false; /* Ever the pessamist! */
+ struct object_functions *pObject = NULL;
+
+ pObject = Device_Objects_Find_Functions(object_type);
+ if (pObject != NULL) {
+ if (pObject->Object_Value_List) {
+ status = true;
+ }
+ }
+
+ return (status);
+}
+
+/** Initialize the Device Object.
+ Initialize the group of object helper functions for any supported Object.
+ Initialize each of the Device Object child Object instances.
+ * param: The BACnet Object Name of the bacnet server
+ */
+void Device_Init(
+ const char * Device_Object_Name)
+{
+ struct object_functions *pObject = NULL;
+
+ /* initialize the Device_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Device_Properties_List + len,
+ Device_Properties_Required);
+ len += BACnet_Init_Properties_List(Device_Properties_List + len,
+ Device_Properties_Optional);
+ len += BACnet_Init_Properties_List(Device_Properties_List + len,
+ Device_Properties_Proprietary);
+
+ characterstring_init_ansi(&My_Object_Name, Device_Object_Name);
+ Object_Table = &My_Object_Table[0]; // sets glogbal variable!
+ pObject = Object_Table;
+ while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
+ if (pObject->Object_Init) {
+ pObject->Object_Init();
+ }
+ pObject++;
+ }
+}
+
+bool DeviceGetRRInfo(
+ BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */
+ RR_PROP_INFO * pInfo)
+{ /* Where to put the response */
+ bool status = false; /* return value */
+
+ switch (pRequest->object_property) {
+ case PROP_VT_CLASSES_SUPPORTED:
+ case PROP_ACTIVE_VT_SESSIONS:
+ case PROP_LIST_OF_SESSION_KEYS:
+ case PROP_TIME_SYNCHRONIZATION_RECIPIENTS:
+ case PROP_MANUAL_SLAVE_ADDRESS_BINDING:
+ case PROP_SLAVE_ADDRESS_BINDING:
+ case PROP_RESTART_NOTIFICATION_RECIPIENTS:
+ case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS:
+ pInfo->RequestTypes = RR_BY_POSITION;
+ pRequest->error_class = ERROR_CLASS_PROPERTY;
+ pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+
+ case PROP_DEVICE_ADDRESS_BINDING:
+ pInfo->RequestTypes = RR_BY_POSITION;
+ pInfo->Handler = rr_address_list_encode;
+ status = true;
+ break;
+
+ default:
+ pRequest->error_class = ERROR_CLASS_SERVICES;
+ pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+ break;
+ }
+
+ return status;
+}
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/device.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,369 @@
+/**************************************************************************
+*
+* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/** device.h Defines functions for handling all BACnet objects belonging
+ * to a BACnet device, as well as Device-specific properties. */
+
+#ifndef DEVICE_H
+#define DEVICE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacenum.h"
+#include "wp.h"
+#include "rd.h"
+#include "rp.h"
+#include "rpm.h"
+#include "readrange.h"
+
+/** Called so a BACnet object can perform any necessary initialization.
+ */
+typedef void (
+ *object_init_function) (
+ void);
+
+/** Counts the number of objects of this type.
+ * return: Count of implemented objects of this type.
+ */
+typedef unsigned (
+ *object_count_function) (
+ void);
+
+/** Maps an object index position to its corresponding BACnet object instance number.
+ * param: index [in] The index of the object, in the array of objects of its type.
+ * return: The BACnet object instance number to be used in a BACNET_OBJECT_ID.
+ */
+typedef uint32_t(
+ *object_index_to_instance_function)
+ (
+ unsigned index);
+
+/** Provides the BACnet Object_Name for a given object instance of this type.
+ * param: object_instance [in] The object instance number to be looked up.
+ * param: object_name [in,out] Pointer to a character_string structure that
+ * will hold a copy of the object name if this is a valid object_instance.
+ * return: True if the object_instance is valid and object_name has been
+ * filled with a copy of the Object's name.
+ */
+typedef bool(
+ *object_name_function)
+ (
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+/** Look in the table of objects of this type, and see if this is a valid
+ * instance number.
+ * param: [in] The object instance number to be looked up.
+ * return: True if the object instance refers to a valid object of this type.
+ */
+typedef bool(
+ *object_valid_instance_function) (
+ uint32_t object_instance);
+
+/** Helper function to step through an array of objects and find either the
+ * first one or the next one of a given type. Used to step through an array
+ * of objects which is not necessarily contiguious for each type i.e. the
+ * index for the 'n'th object of a given type is not necessarily 'n'.
+ * param: [in] The index of the current object or a value of ~0 to indicate
+ * start at the beginning.
+ * return: The index of the next object of the required type or ~0 (all bits
+ * == 1) to indicate no more objects found.
+ */
+typedef unsigned (
+ *object_iterate_function) (
+ unsigned current_index);
+
+/** Look in the table of objects of this type, and get the COV Value List.
+ * param: [in] The object instance number to be looked up.
+ * param: [out] The value list
+ * return: True if the object instance supports this feature, and has changed.
+ */
+typedef bool(
+ *object_value_list_function) (
+ uint32_t object_instance,
+ BACNET_PROPERTY_VALUE * value_list);
+
+/** Look in the table of objects for this instance to see if value changed.
+ * param: [in] The object instance number to be looked up.
+ * return: True if the object instance has changed.
+ */
+typedef bool(
+ *object_cov_function) (
+ uint32_t object_instance);
+
+/** Look in the table of objects for this instance to clear the changed flag.
+ * param: [in] The object instance number to be looked up.
+ */
+typedef void (
+ *object_cov_clear_function) (
+ uint32_t object_instance);
+
+/** Intrinsic Reporting funcionality.
+ * param: [in] Object instance.
+ */
+typedef void (
+ *object_intrinsic_reporting_function) (
+ uint32_t object_instance);
+
+
+/** Defines the group of object helper functions for any supported Object.
+ * Each Object must provide some implementation of each of these helpers
+ * in order to properly support the handlers. Eg, the ReadProperty handler
+ * handler_read_property() relies on the instance of Object_Read_Property
+ * for each Object type, or configure the function as NULL.
+ * In both appearance and operation, this group of functions acts like
+ * they are member functions of a C++ Object base class.
+ */
+typedef struct object_functions {
+ BACNET_OBJECT_TYPE Object_Type;
+ object_init_function Object_Init;
+ object_count_function Object_Count;
+ object_index_to_instance_function Object_Index_To_Instance;
+ object_valid_instance_function Object_Valid_Instance;
+ object_name_function Object_Name;
+ read_property_function Object_Read_Property;
+ write_property_function Object_Write_Property;
+ rpm_property_lists_function Object_RPM_List;
+ rr_info_function Object_RR_Info;
+ object_iterate_function Object_Iterator;
+ object_value_list_function Object_Value_List;
+ object_cov_function Object_COV;
+ object_cov_clear_function Object_COV_Clear;
+ object_intrinsic_reporting_function Object_Intrinsic_Reporting;
+} object_functions_t;
+
+/* String Lengths for Device Object properties that may be changed
+ * (written to) over the BACnet network.
+ * Maximum sizes excluding nul terminator .
+ */
+#define STRLEN_X(minlen, str) (((minlen)>sizeof(str))?(minlen):sizeof(str))
+#define MAX_DEV_NAME_LEN STRLEN_X(32, "%(BACnet_Device_Name)s") /* Device name */
+#define MAX_DEV_LOC_LEN STRLEN_X(64, BACNET_DEVICE_LOCATION) /* Device location */
+#define MAX_DEV_MOD_LEN STRLEN_X(32, BACNET_DEVICE_MODEL_NAME) /* Device model name */
+#define MAX_DEV_VER_LEN STRLEN_X(16, BACNET_DEVICE_APPSOFT_VER) /* Device application software version */
+#define MAX_DEV_DESC_LEN STRLEN_X(64, BACNET_DEVICE_DESCRIPTION) /* Device description */
+
+/** Structure to define the Object Properties common to all Objects. */
+typedef struct commonBacObj_s {
+
+ /** The BACnet type of this object (ie, what class is this object from?).
+ * This property, of type BACnetObjectType, indicates membership in a
+ * particular object type class. Each inherited class will be of one type.
+ */
+ BACNET_OBJECT_TYPE mObject_Type;
+
+ /** The instance number for this class instance. */
+ uint32_t Object_Instance_Number;
+
+ /** Object Name; must be unique.
+ * This property, of type CharacterString, shall represent a name for
+ * the object that is unique within the BACnet Device that maintains it.
+ */
+ char Object_Name[MAX_DEV_NAME_LEN];
+
+} COMMON_BAC_OBJECT;
+
+
+/** Structure to define the Properties of Device Objects which distinguish
+ * one instance from another.
+ * This structure only defines fields for properties that are unique to
+ * a given Device object. The rest may be fixed in device.c or hard-coded
+ * into the read-property encoding.
+ * This may be useful for implementations which manage multiple Devices,
+ * eg, a Gateway.
+ */
+typedef struct devObj_s {
+ /** The BACnet Device Address for this device; ->len depends on DLL type. */
+ BACNET_ADDRESS bacDevAddr;
+
+ /** Structure for the Object Properties common to all Objects. */
+ COMMON_BAC_OBJECT bacObj;
+
+ /** Device Description. */
+ char Description[MAX_DEV_DESC_LEN];
+
+ /** The upcounter that shows if the Device ID or object structure has changed. */
+ uint32_t Database_Revision;
+} DEVICE_OBJECT_DATA;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+ void Device_Init(
+ const char * Device_Object_Name);
+
+ bool Device_Reinitialize(
+ BACNET_REINITIALIZE_DEVICE_DATA * rd_data);
+
+ BACNET_REINITIALIZED_STATE Device_Reinitialized_State(
+ void);
+
+ rr_info_function Device_Objects_RR_Info(
+ BACNET_OBJECT_TYPE object_type);
+
+ void Device_getCurrentDateTime(
+ BACNET_DATE_TIME * DateTime);
+ int32_t Device_UTC_Offset(void);
+ bool Device_Daylight_Savings_Status(void);
+
+ void Device_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+ void Device_Objects_Property_List(
+ BACNET_OBJECT_TYPE object_type,
+ struct special_property_list_t *pPropertyList);
+ /* functions to support COV */
+ bool Device_Encode_Value_List(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance,
+ BACNET_PROPERTY_VALUE * value_list);
+ bool Device_Value_List_Supported(
+ BACNET_OBJECT_TYPE object_type);
+ bool Device_COV(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance);
+ void Device_COV_Clear(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance);
+
+ uint32_t Device_Object_Instance_Number(
+ void);
+ bool Device_Set_Object_Instance_Number(
+ uint32_t object_id);
+ bool Device_Valid_Object_Instance_Number(
+ uint32_t object_id);
+ unsigned Device_Object_List_Count(
+ void);
+ bool Device_Object_List_Identifier(
+ unsigned array_index,
+ int *object_type,
+ uint32_t * instance);
+
+ unsigned Device_Count(
+ void);
+ uint32_t Device_Index_To_Instance(
+ unsigned index);
+
+ bool Device_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+ bool Device_Set_Object_Name(
+ BACNET_CHARACTER_STRING * object_name);
+ /* Copy a child object name, given its ID. */
+ bool Device_Object_Name_Copy(
+ BACNET_OBJECT_TYPE object_type,
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+ bool Device_Object_Name_ANSI_Init(const char * value);
+
+ BACNET_DEVICE_STATUS Device_System_Status(
+ void);
+ int Device_Set_System_Status(
+ BACNET_DEVICE_STATUS status,
+ bool local);
+
+ const char *Device_Vendor_Name(
+ void);
+
+ uint16_t Device_Vendor_Identifier(
+ void);
+ void Device_Set_Vendor_Identifier(
+ uint16_t vendor_id);
+
+ const char *Device_Model_Name(
+ void);
+ bool Device_Set_Model_Name(
+ const char *name,
+ size_t length);
+
+ const char *Device_Firmware_Revision(
+ void);
+
+ const char *Device_Application_Software_Version(
+ void);
+ bool Device_Set_Application_Software_Version(
+ const char *name,
+ size_t length);
+
+ const char *Device_Description(
+ void);
+ bool Device_Set_Description(
+ const char *name,
+ size_t length);
+
+ const char *Device_Location(
+ void);
+ bool Device_Set_Location(
+ const char *name,
+ size_t length);
+
+ /* some stack-centric constant values - no set methods */
+ uint8_t Device_Protocol_Version(
+ void);
+ uint8_t Device_Protocol_Revision(
+ void);
+ BACNET_SEGMENTATION Device_Segmentation_Supported(
+ void);
+
+ uint32_t Device_Database_Revision(
+ void);
+ void Device_Set_Database_Revision(
+ uint32_t revision);
+ void Device_Inc_Database_Revision(
+ void);
+
+ bool Device_Valid_Object_Name(
+ BACNET_CHARACTER_STRING * object_name,
+ int *object_type,
+ uint32_t * object_instance);
+ bool Device_Valid_Object_Id(
+ int object_type,
+ uint32_t object_instance);
+
+ int Device_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+ bool Device_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ bool DeviceGetRRInfo(
+ BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */
+ RR_PROP_INFO * pInfo); /* Where to put the information */
+
+ int Device_Read_Property_Local(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+ bool Device_Write_Property_Local(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msi.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,556 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Input Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz pluginh */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "msi_%(locstr)s.h"
+#include "handlers.h"
+
+
+/* initial value for present_value property of each object */
+#define MSI_VALUE_INIT (1)
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Input objects of BACnet protocol */
+%(MSI_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Input Objects */
+#define MAX_MULTISTATE_INPUTS %(MSI_count)s
+static MULTISTATE_INPUT_DESCR MSI_Descr[MAX_MULTISTATE_INPUTS] = {
+%(MSI_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Input_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_NUMBER_OF_STATES, /* R R ( 74) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Multistate_Input_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ PROP_STATE_TEXT, /* R O (110) */
+ -1
+};
+
+static const int Multistate_Input_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Multistate_Input_Init() based off the values
+ * stored in Multistate_Input_Properties_Required
+ * Multistate_Input_Properties_Optional
+ * Multistate_Input_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Input_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Multistate_Input_Properties_Required;
+ if (pOptional)
+ *pOptional = Multistate_Input_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Multistate_Input_Properties_Proprietary;
+
+ return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Input_Init(
+ void)
+{
+ unsigned int i, j;
+
+ /* initialize the Multistate_Input_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+ Multistate_Input_Properties_Required);
+ len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+ Multistate_Input_Properties_Optional);
+ len += BACnet_Init_Properties_List(Multistate_Input_Properties_List + len,
+ Multistate_Input_Properties_Proprietary);
+
+ /* initialize all the analog output priority arrays to NULL */
+ for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+ MSI_Descr[i].Present_Value = MSI_VALUE_INIT;
+ for (j = 0; j < MSI_Descr[i].Number_Of_States; j++) {
+ sprintf(MSI_Descr[i].State_Text[j], "State %%d", j+1);
+ }
+ }
+ return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Input_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Multistate_Input_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Multistate_Input_Instance_To_Index(object_instance) < MAX_MULTISTATE_INPUTS);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Multistate_Input_Count(void) {return MAX_MULTISTATE_INPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Multistate_Input_Index_To_Instance(unsigned index) {return MSI_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Input_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_MULTISTATE_INPUTS; index++)
+ if (object_instance == MSI_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_MULTISTATE_INPUTS;
+}
+
+
+
+
+
+uint32_t Multistate_Input_Present_Value(
+ uint32_t object_instance)
+{
+ uint32_t value = MSI_VALUE_INIT;
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_INPUTS)
+ value = MSI_Descr[index].Present_Value;
+
+ return value;
+}
+
+
+
+bool Multistate_Input_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value)
+{
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index >= MAX_MULTISTATE_INPUTS)
+ return false;
+
+ if ((value == 0) || (value > MSI_Descr[index].Number_Of_States))
+ return false;
+
+ MSI_Descr[index].Present_Value = (uint8_t) value;
+ return true;
+}
+
+
+
+
+bool Multistate_Input_Out_Of_Service(
+ uint32_t object_instance)
+{
+ bool value = false;
+ unsigned index = 0;
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_INPUTS) {
+ value = MSI_Descr[index].Out_Of_Service;
+ }
+
+ return value;
+}
+
+
+
+void Multistate_Input_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value)
+{
+ unsigned index = 0;
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_INPUTS) {
+ MSI_Descr[index].Out_Of_Service = value;
+ }
+
+ return;
+}
+
+
+
+static char *Multistate_Input_Description(
+ uint32_t object_instance)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ char *pName = NULL; /* return value */
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_INPUTS) {
+ pName = MSI_Descr[index].Description;
+ }
+
+ return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ bool status = false;
+
+ index = Multistate_Input_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_INPUTS) {
+ status = characterstring_init_ansi(object_name, MSI_Descr[index].Object_Name);
+ }
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Multistate_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ uint32_t present_value = 0;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Multistate_Input_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_MULTISTATE_INPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0],
+ OBJECT_MULTI_STATE_INPUT, rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Multistate_Input_Object_Name(rpdata->object_instance,
+ &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ characterstring_init_ansi(&char_string,
+ Multistate_Input_Description(rpdata->object_instance));
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0],
+ OBJECT_MULTI_STATE_INPUT);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Multistate_Input_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ if (Multistate_Input_Out_Of_Service(rpdata->object_instance)) {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ true);
+ } else {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ false);
+ }
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = MSI_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_NUMBER_OF_STATES:
+ apdu_len =
+ encode_application_unsigned(&apdu[apdu_len],
+ MSI_Descr[object_index].Number_Of_States);
+ break;
+ case PROP_STATE_TEXT:
+ BACnet_encode_array(MSI_Descr[object_index].State_Text,
+ MSI_Descr[object_index].Number_Of_States,
+ retfalse, BACnet_encode_character_string);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Multistate_Input_Properties_List,
+// property_list_count(Multistate_Input_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ unsigned object_index = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_STATE_TEXT) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Multistate_Input_Write_Property() is called
+ */
+ object_index = Multistate_Input_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ status = false; // not really necessary here.
+ } else {
+ if (!MSI_Descr[object_index].Out_Of_Service) {
+ /* input objects can only be written to when Out_Of_Service is true! */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ status = false;
+ } else {
+ status =
+ Multistate_Input_Present_Value_Set
+ (wp_data->object_instance, value.type.Unsigned_Int);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ status = false; // not really necessary here.
+ }
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = MSI_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ MSI_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !MSI_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ MSI_Descr[object_index].Present_Value =
+ *(MSI_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_NUMBER_OF_STATES:
+ case PROP_DESCRIPTION:
+ case PROP_STATE_TEXT:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Multistate_Input_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSI_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(MSI_Descr[i].Located_Var_ptr) = Multistate_Input_Present_Value(MSI_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Multistate_Input_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_INPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSI_Descr[i].Out_Of_Service)
+ continue;
+
+ // Make sure local device does not set Present_Value to 0, nor higher than Number_Of_States
+ if ((*(MSI_Descr[i].Located_Var_ptr) > MSI_Descr[i].Number_Of_States) ||
+ (*(MSI_Descr[i].Located_Var_ptr) == 0))
+ continue;
+
+ // Everything seems to be OK. Copy the value
+ MSI_Descr[i].Present_Value = *(MSI_Descr[i].Located_Var_ptr);
+ }
+}
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msi.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,109 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_INPUT_H
+#define MULTISTATE_INPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Inputs are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+ typedef struct Multistate_Input_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint8_t Number_Of_States;
+
+ /* stores the current value */
+ uint8_t Present_Value;
+ /* Writable out-of-service allows others to manipulate our Present Value */
+ bool Out_Of_Service;
+ char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+ } MULTISTATE_INPUT_DESCR;
+
+
+ void Multistate_Input_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+
+ bool Multistate_Input_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Multistate_Input_Count(
+ void);
+ uint32_t Multistate_Input_Index_To_Instance(
+ unsigned index);
+ unsigned Multistate_Input_Instance_To_Index(
+ uint32_t instance);
+
+ int Multistate_Input_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Multistate_Input_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ /* optional API */
+ bool Multistate_Input_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ uint32_t Multistate_Input_Present_Value(
+ uint32_t object_instance);
+ bool Multistate_Input_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value);
+
+ bool Multistate_Input_Out_Of_Service(
+ uint32_t object_instance);
+ void Multistate_Input_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value);
+
+ void Multistate_Input_Init(
+ void);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/mso.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,668 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Output Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz pluginh */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "mso_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since multi state value objects can never take the value 0, we
+ * use 0 as our NULL value.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MSO_VALUE_NULL (0)
+#define MSO_VALUE_IS_NULL(x) ((x) == MSO_VALUE_NULL)
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define MSO_VALUE_RELINQUISH_DEFAULT 1
+
+
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Output objects of BACnet protocol */
+%(MSO_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Output Objects */
+#define MAX_MULTISTATE_OUTPUTS %(MSO_count)s
+static MULTISTATE_OUTPUT_DESCR MSO_Descr[MAX_MULTISTATE_OUTPUTS] = {
+%(MSO_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Output_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W W ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_NUMBER_OF_STATES, /* R R ( 74) */
+ PROP_PRIORITY_ARRAY, /* R R ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R R (104) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R R (431) */
+ -1
+};
+
+static const int Multistate_Output_Properties_Optional[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_DESCRIPTION, /* R O ( 28) */
+ PROP_STATE_TEXT, /* R O (110) */
+ -1
+};
+
+static const int Multistate_Output_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Multistate_Output_Init() based off the values
+ * stored in Multistate_Output_Properties_Required
+ * Multistate_Output_Properties_Optional
+ * Multistate_Output_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Output_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Multistate_Output_Properties_Required;
+ if (pOptional)
+ *pOptional = Multistate_Output_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Multistate_Output_Properties_Proprietary;
+
+ return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Output_Init(
+ void)
+{
+ unsigned int i, j;
+
+ /* initialize the Multistate_Output_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+ Multistate_Output_Properties_Required);
+ len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+ Multistate_Output_Properties_Optional);
+ len += BACnet_Init_Properties_List(Multistate_Output_Properties_List + len,
+ Multistate_Output_Properties_Proprietary);
+
+ /* initialize all the analog output priority arrays to NULL */
+ for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ MSO_Descr[i].Present_Value[j] = MSO_VALUE_NULL;
+ }
+ for (j = 0; j < MSO_Descr[i].Number_Of_States; j++) {
+ sprintf(MSO_Descr[i].State_Text[j], "State %%d", j+1);
+ }
+ }
+ return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Output_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Multistate_Output_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Multistate_Output_Instance_To_Index(object_instance) < MAX_MULTISTATE_OUTPUTS);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Multistate_Output_Count(void) {return MAX_MULTISTATE_OUTPUTS;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Multistate_Output_Index_To_Instance(unsigned index) {return MSO_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Output_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_MULTISTATE_OUTPUTS; index++)
+ if (object_instance == MSO_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_MULTISTATE_OUTPUTS;
+}
+
+
+
+
+
+uint32_t Multistate_Output_Present_Value(
+ uint32_t object_instance)
+{
+ uint32_t value = MSO_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0; /* offset from instance lookup */
+ unsigned i = 0;
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!MSO_VALUE_IS_NULL(MSO_Descr[index].Present_Value[i])) {
+ value = MSO_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+bool Multistate_Output_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value,
+ uint8_t priority)
+{
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index >= MAX_MULTISTATE_OUTPUTS)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ if ((value == 0) || (value > MSO_Descr[index].Number_Of_States))
+ return false;
+
+ priority--;
+ MSO_Descr[index].Present_Value[priority] = (uint8_t) value;
+
+ return true;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Multistate_Output_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!MSO_VALUE_IS_NULL(MSO_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+
+bool Multistate_Output_Present_Value_Relinquish(
+ uint32_t object_instance,
+ uint8_t priority)
+{
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index >= MAX_MULTISTATE_OUTPUTS)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ MSO_Descr[index].Present_Value[priority] = MSO_VALUE_NULL;
+
+ return true;
+}
+
+
+
+bool Multistate_Output_Out_Of_Service(
+ uint32_t object_instance)
+{
+ bool value = false;
+ unsigned index = 0;
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ value = MSO_Descr[index].Out_Of_Service;
+ }
+
+ return value;
+}
+
+
+
+void Multistate_Output_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value)
+{
+ unsigned index = 0;
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ MSO_Descr[index].Out_Of_Service = value;
+ }
+
+ return;
+}
+
+
+
+static char *Multistate_Output_Description(
+ uint32_t object_instance)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ char *pName = NULL; /* return value */
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ pName = MSO_Descr[index].Description;
+ }
+
+ return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ bool status = false;
+
+ index = Multistate_Output_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_OUTPUTS) {
+ status = characterstring_init_ansi(object_name, MSO_Descr[index].Object_Name);
+ }
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Multistate_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ uint32_t present_value = 0;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Multistate_Output_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_MULTISTATE_OUTPUTS) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0],
+ OBJECT_MULTI_STATE_OUTPUT, rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Multistate_Output_Object_Name(rpdata->object_instance,
+ &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ characterstring_init_ansi(&char_string,
+ Multistate_Output_Description(rpdata->object_instance));
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0],
+ OBJECT_MULTI_STATE_OUTPUT);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Multistate_Output_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ if (Multistate_Output_Out_Of_Service(rpdata->object_instance)) {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ true);
+ } else {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ false);
+ }
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = MSO_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(MSO_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ MSO_VALUE_IS_NULL,
+ encode_application_unsigned);
+ break;
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Multistate_Output_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+
+ case PROP_RELINQUISH_DEFAULT:
+ present_value = MSO_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_unsigned(&apdu[0], present_value);
+ break;
+ case PROP_NUMBER_OF_STATES:
+ apdu_len =
+ encode_application_unsigned(&apdu[apdu_len],
+ MSO_Descr[object_index].Number_Of_States);
+ break;
+ case PROP_STATE_TEXT:
+ BACnet_encode_array(MSO_Descr[object_index].State_Text,
+ MSO_Descr[object_index].Number_Of_States,
+ retfalse, BACnet_encode_character_string);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Multistate_Output_Properties_List,
+// property_list_count(Multistate_Output_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+ (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ unsigned object_index = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_STATE_TEXT) &&
+ (wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Multistate_Output_Write_Property() is called
+ */
+ object_index = Multistate_Output_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Multistate_Output_Present_Value_Set
+ (wp_data->object_instance, value.type.Unsigned_Int, wp_data->priority);
+ if (!status) {
+ if (wp_data->priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Multistate_Output_Present_Value_Relinquish
+ (wp_data->object_instance, wp_data->priority);
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = MSO_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ MSO_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !MSO_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ MSO_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(MSO_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_NUMBER_OF_STATES:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ case PROP_RELINQUISH_DEFAULT:
+ case PROP_DESCRIPTION:
+ case PROP_STATE_TEXT:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Multistate_Output_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSO_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(MSO_Descr[i].Located_Var_ptr) = Multistate_Output_Present_Value(MSO_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Multistate_Output_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSO_Descr[i].Out_Of_Service)
+ continue;
+
+ // Make sure local device does not set Present_Value to a value higher than Number_Of_States
+ /* NOTE: The following comparison (Present_Value > Number_Of_States) is OK as the
+ * MSO_VALUE_NULL is currently set to 0. This means that the local controller
+ * can safely relinquish control by setting the Present_Value to 0.
+ * The comparison would not be safe if for some reason we later change
+ * MSO_VALUE_NULL to a value that may be higher than Number_Of_States.
+ */
+ #if MSO_VALUE_NULL != 0
+ #error Implementation assumes that MSO_VALUE_NULL is set to 0, which is currently not the case.
+ #endif
+ if (*(MSO_Descr[i].Located_Var_ptr) > MSO_Descr[i].Number_Of_States)
+ continue;
+
+ // Everything seems to be OK. Copy the value
+ MSO_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(MSO_Descr[i].Located_Var_ptr);
+ }
+}
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/mso.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,110 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_OUTPUT_H
+#define MULTISTATE_OUTPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Outputs are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+ typedef struct Multistate_Output_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint8_t Number_Of_States;
+
+ /* stores the current value */
+ /* one entry per priority value */
+ uint8_t Present_Value[BACNET_MAX_PRIORITY];
+ /* Writable out-of-service allows others to manipulate our Present Value */
+ bool Out_Of_Service;
+ char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+ } MULTISTATE_OUTPUT_DESCR;
+
+
+ void Multistate_Output_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+
+ bool Multistate_Output_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Multistate_Output_Count(
+ void);
+ uint32_t Multistate_Output_Index_To_Instance(
+ unsigned index);
+ unsigned Multistate_Output_Instance_To_Index(
+ uint32_t instance);
+
+ int Multistate_Output_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Multistate_Output_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ /* optional API */
+ bool Multistate_Output_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ uint32_t Multistate_Output_Present_Value(
+ uint32_t object_instance);
+ bool Multistate_Output_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value,
+ uint8_t priority);
+
+ bool Multistate_Output_Out_Of_Service(
+ uint32_t object_instance);
+ void Multistate_Output_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value);
+
+ void Multistate_Output_Init(
+ void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msv.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,666 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+/* Multi-state Value Objects */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz pluginh */
+#include "bacdef.h"
+#include "bacdcode.h"
+#include "bacenum.h"
+#include "bacapp.h"
+#include "rp.h"
+#include "wp.h"
+#include "msv_%(locstr)s.h"
+#include "handlers.h"
+
+/* we choose to have a NULL level in our system represented by */
+/* a particular value. When the priorities are not in use, they */
+/* will be relinquished (i.e. set to the NULL level). */
+/* Since multi state value objects can never take the value 0, we
+ * use 0 as our NULL value.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MSV_VALUE_NULL (0)
+#define MSV_VALUE_IS_NULL(x) ((x) == MSV_VALUE_NULL)
+/* When all the priorities are level null, the present value returns */
+/* the Relinquish Default value */
+#define MSV_VALUE_RELINQUISH_DEFAULT 1
+
+
+
+/* The IEC 61131-3 located variables mapped onto the Multi-state Value objects of BACnet protocol */
+%(MSV_lvars)s
+
+
+/* The array where we keep all the state related to the Multi-state Value Objects */
+#define MAX_MULTISTATE_VALUES %(MSV_count)s
+static MULTISTATE_VALUE_DESCR MSV_Descr[MAX_MULTISTATE_VALUES] = {
+%(MSV_param)s
+};
+
+
+
+/* These three arrays are used by the ReadPropertyMultiple handler,
+ * as well as to initialize the XXX_Property_List used by the
+ * Property List (PROP_PROPERTY_LIST) property.
+ */
+static const int Multistate_Value_Properties_Required[] = {
+ /* (1) Currently Supported */
+ /* (2) Required by standard ASHRAE 135-2016 */
+ /*(1)(2) */
+ PROP_OBJECT_IDENTIFIER, /* R R ( 75) */
+ PROP_OBJECT_NAME, /* R R ( 77) */
+ PROP_OBJECT_TYPE, /* R R ( 79) */
+ PROP_PRESENT_VALUE, /* W R ( 85) */
+ PROP_STATUS_FLAGS, /* R R (111) */
+ PROP_EVENT_STATE, /* R R ( 36) */
+ PROP_OUT_OF_SERVICE, /* W R ( 81) */
+ PROP_NUMBER_OF_STATES, /* R R ( 74) */
+// PROP_PROPERTY_LIST, /* R R (371) */
+ -1
+};
+
+static const int Multistate_Value_Properties_Optional[] = {
+ PROP_DESCRIPTION, /* R O ( 28) */
+ PROP_STATE_TEXT, /* R O (110) */
+ /* required if Present_Value is writable (which is true in our case!) */
+ PROP_PRIORITY_ARRAY, /* R O ( 87) */
+ PROP_RELINQUISH_DEFAULT, /* R O (104) */
+// PROP_CURRENT_COMMAND_PRIORITY,/* R O (431) */
+ -1
+};
+
+static const int Multistate_Value_Properties_Proprietary[] = {
+ -1
+};
+
+
+/* This array stores the PROPERTY_LIST which may be read by clients.
+ * End of list is marked by following the last element with the value '-1'
+ *
+ * It is initialized by Multistate_Value_Init() based off the values
+ * stored in Multistate_Value_Properties_Required
+ * Multistate_Value_Properties_Optional
+ * Multistate_Value_Properties_Proprietary
+ */
+/* TODO: Allocate memory for this array with malloc() at startup */
+static int Multistate_Value_Properties_List[64];
+
+
+
+
+
+/********************************************************/
+/** Callback functions. **/
+/** Functions required by BACnet devie implementation. **/
+/********************************************************/
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary)
+{
+ if (pRequired)
+ *pRequired = Multistate_Value_Properties_Required;
+ if (pOptional)
+ *pOptional = Multistate_Value_Properties_Optional;
+ if (pProprietary)
+ *pProprietary = Multistate_Value_Properties_Proprietary;
+
+ return;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+void Multistate_Value_Init(
+ void)
+{
+ unsigned int i, j;
+
+ /* initialize the Multistate_Value_Properties_List array */
+ int len = 0;
+ len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+ Multistate_Value_Properties_Required);
+ len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+ Multistate_Value_Properties_Optional);
+ len += BACnet_Init_Properties_List(Multistate_Value_Properties_List + len,
+ Multistate_Value_Properties_Proprietary);
+
+ /* initialize all the analog output priority arrays to NULL */
+ for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+ for (j = 0; j < BACNET_MAX_PRIORITY; j++) {
+ MSV_Descr[i].Present_Value[j] = MSV_VALUE_NULL;
+ }
+ for (j = 0; j < MSV_Descr[i].Number_Of_States; j++) {
+ sprintf(MSV_Descr[i].State_Text[j], "State %%d", j+1);
+ }
+ }
+ return;
+}
+
+
+
+/* validate that the given instance exists */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Value_Valid_Instance(
+ uint32_t object_instance)
+{
+ // fprintf(stderr, "BACnet plugin: Multistate_Value_Valid_Instance(obj_ID=%%u) called!\n", object _instance);
+ return (Multistate_Value_Instance_To_Index(object_instance) < MAX_MULTISTATE_VALUES);
+}
+
+
+/* the number of Multistate Value Objects */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+unsigned Multistate_Value_Count(void) {return MAX_MULTISTATE_VALUES;}
+
+
+/* returns the instance (i.e. Object Identifier) that correlates to the correct index */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+uint32_t Multistate_Value_Index_To_Instance(unsigned index) {return MSV_Descr[index].Object_Identifier;}
+
+
+
+
+
+
+/* returns the index that correlates to the correct instance number (Object Identifier) */
+unsigned Multistate_Value_Instance_To_Index(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+
+ for (index = 0; index < MAX_MULTISTATE_VALUES; index++)
+ if (object_instance == MSV_Descr[index].Object_Identifier)
+ return index;
+
+ /* error, this object ID is not in our list! */
+ return MAX_MULTISTATE_VALUES;
+}
+
+
+
+
+
+uint32_t Multistate_Value_Present_Value(
+ uint32_t object_instance)
+{
+ uint32_t value = MSV_VALUE_RELINQUISH_DEFAULT;
+ unsigned index = 0; /* offset from instance lookup */
+ unsigned i = 0;
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!MSV_VALUE_IS_NULL(MSV_Descr[index].Present_Value[i])) {
+ value = MSV_Descr[index].Present_Value[i];
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+
+
+bool Multistate_Value_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value,
+ uint8_t priority)
+{
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index >= MAX_MULTISTATE_VALUES)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ if ((value == 0) || (value > MSV_Descr[index].Number_Of_States))
+ return false;
+
+ priority--;
+ MSV_Descr[index].Present_Value[priority] = (uint8_t) value;
+
+ return true;
+}
+
+
+
+/* returns command priority (1..16), or 0 if all priority values are at NULL */
+int Multistate_Value_Current_Command_Priority(
+ uint32_t object_instance)
+{
+ unsigned index = 0;
+ unsigned i = 0;
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
+ if (!MSV_VALUE_IS_NULL(MSV_Descr[index].Present_Value[i])) {
+ return i+1; // +1 since priority is 1..16, and not 0..15
+ }
+ }
+ }
+ // command values in all priorities are set to NULL
+ return 0;
+}
+
+
+
+
+bool Multistate_Value_Present_Value_Relinquish(
+ uint32_t object_instance,
+ uint8_t priority)
+{
+ unsigned index = 0; /* offset from instance lookup */
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index >= MAX_MULTISTATE_VALUES)
+ return false;
+
+ if ((priority == 0) || (priority > BACNET_MAX_PRIORITY) ||
+ (priority == 6 /* reserved, ASHRAE 135-2016, section 19.2.2 */))
+ return false;
+
+ priority--;
+ MSV_Descr[index].Present_Value[priority] = MSV_VALUE_NULL;
+
+ return true;
+}
+
+
+
+bool Multistate_Value_Out_Of_Service(
+ uint32_t object_instance)
+{
+ bool value = false;
+ unsigned index = 0;
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ value = MSV_Descr[index].Out_Of_Service;
+ }
+
+ return value;
+}
+
+
+
+void Multistate_Value_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value)
+{
+ unsigned index = 0;
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ MSV_Descr[index].Out_Of_Service = value;
+ }
+
+ return;
+}
+
+
+
+static char *Multistate_Value_Description(
+ uint32_t object_instance)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ char *pName = NULL; /* return value */
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ pName = MSV_Descr[index].Description;
+ }
+
+ return pName;
+}
+
+
+
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name)
+{
+ unsigned index = 0; /* offset from instance lookup */
+ bool status = false;
+
+ index = Multistate_Value_Instance_To_Index(object_instance);
+ if (index < MAX_MULTISTATE_VALUES) {
+ status = characterstring_init_ansi(object_name, MSV_Descr[index].Object_Name);
+ }
+
+ return status;
+}
+
+
+
+/* return apdu len, or BACNET_STATUS_ERROR on error */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+int Multistate_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata)
+{
+ int len = 0;
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ uint32_t present_value = 0;
+ unsigned object_index = 0;
+ unsigned i = 0;
+ bool state = false;
+ uint8_t *apdu = NULL;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+
+ object_index = Multistate_Value_Instance_To_Index(rpdata->object_instance);
+ if (object_index >= MAX_MULTISTATE_VALUES) {
+ rpdata->error_class = ERROR_CLASS_OBJECT;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return BACNET_STATUS_ERROR;
+ }
+
+ apdu = rpdata->application_data;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len =
+ encode_application_object_id(&apdu[0],
+ OBJECT_MULTI_STATE_VALUE, rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Multistate_Value_Object_Name(rpdata->object_instance,
+ &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_DESCRIPTION:
+ characterstring_init_ansi(&char_string,
+ Multistate_Value_Description(rpdata->object_instance));
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len =
+ encode_application_enumerated(&apdu[0],
+ OBJECT_MULTI_STATE_VALUE);
+ break;
+ case PROP_PRESENT_VALUE:
+ present_value =
+ Multistate_Value_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], present_value);
+ break;
+ case PROP_STATUS_FLAGS:
+ /* note: see the details in the standard on how to use these */
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ if (Multistate_Value_Out_Of_Service(rpdata->object_instance)) {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ true);
+ } else {
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
+ false);
+ }
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_EVENT_STATE:
+ /* note: see the details in the standard on how to use this */
+ apdu_len =
+ encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = MSV_Descr[object_index].Out_Of_Service;
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_PRIORITY_ARRAY:
+ BACnet_encode_array(MSV_Descr[object_index].Present_Value,
+ BACNET_MAX_PRIORITY,
+ MSV_VALUE_IS_NULL,
+ encode_application_unsigned);
+ break;
+// case PROP_CURRENT_COMMAND_PRIORITY: {
+// unsigned i = Multistate_Value_Current_Command_Priority(rpdata->object_instance);
+// if (i == 0) apdu_len = encode_application_null (&apdu[0]);
+// else apdu_len = encode_application_unsigned(&apdu[0], i);
+// break;
+// }
+ case PROP_RELINQUISH_DEFAULT:
+ present_value = MSV_VALUE_RELINQUISH_DEFAULT;
+ apdu_len = encode_application_unsigned(&apdu[0], present_value);
+ break;
+ case PROP_NUMBER_OF_STATES:
+ apdu_len =
+ encode_application_unsigned(&apdu[apdu_len],
+ MSV_Descr[object_index].Number_Of_States);
+ break;
+ case PROP_STATE_TEXT:
+ BACnet_encode_array(MSV_Descr[object_index].State_Text,
+ MSV_Descr[object_index].Number_Of_States,
+ retfalse, BACnet_encode_character_string);
+ break;
+// case PROP_PROPERTY_LIST:
+// BACnet_encode_array(Multistate_Value_Properties_List,
+// property_list_count(Multistate_Value_Properties_List),
+// retfalse, encode_application_enumerated);
+// break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+ /* only array properties can have array options */
+ if ((apdu_len >= 0) && (rpdata->object_property != PROP_STATE_TEXT) &&
+ (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
+// (rpdata->object_property != PROP_PROPERTY_LIST) &&
+ (rpdata->array_index != BACNET_ARRAY_ALL)) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+/* returns true if successful */
+/* This is a callback function. Callback set in My_Object_Table[] array declared in device.c, */
+bool Multistate_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ unsigned object_index = 0;
+ BACNET_APPLICATION_DATA_VALUE value;
+
+ /* decode the some of the request */
+ len =
+ bacapp_decode_application_data(wp_data->application_data,
+ wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ if ((wp_data->object_property != PROP_STATE_TEXT) &&
+ (wp_data->object_property != PROP_PRIORITY_ARRAY) &&
+ (wp_data->array_index != BACNET_ARRAY_ALL)) {
+ /* only array properties can have array options */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ return false;
+ }
+ /* No need to check whether object_index is within bounds.
+ * Has already been checked before Multistate_Output_Write_Property() is called
+ */
+ object_index = Multistate_Value_Instance_To_Index(wp_data->object_instance);
+
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Multistate_Value_Present_Value_Set
+ (wp_data->object_instance, value.type.Unsigned_Int, wp_data->priority);
+ if (!status) {
+ if (wp_data->priority == 6) {
+ /* Command priority 6 is reserved for use by Minimum On/Off
+ algorithm and may not be used for other purposes in any
+ object. */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ } else {
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ status =
+ Multistate_Value_Present_Value_Relinquish
+ (wp_data->object_instance, wp_data->priority);
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ {
+ bool Previous_Out_Of_Service = MSV_Descr[object_index].Out_Of_Service;
+ status =
+ WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN,
+ &wp_data->error_class, &wp_data->error_code);
+ if (status) {
+ MSV_Descr[object_index].Out_Of_Service = value.type.Boolean;
+ if (Previous_Out_Of_Service && !MSV_Descr[object_index].Out_Of_Service)
+ /* We have just changed from Out_of_Service -> In Service */
+ /* We need to update the Present_Value to the value
+ * currently in the PLC...
+ */
+ MSV_Descr[object_index].Present_Value[BACNET_MAX_PRIORITY-1] =
+ *(MSV_Descr[object_index].Located_Var_ptr);
+ }
+ break;
+ }
+ case PROP_OBJECT_IDENTIFIER:
+ case PROP_OBJECT_NAME:
+ case PROP_OBJECT_TYPE:
+ case PROP_STATUS_FLAGS:
+ case PROP_EVENT_STATE:
+ case PROP_NUMBER_OF_STATES:
+ case PROP_PRIORITY_ARRAY:
+// case PROP_CURRENT_COMMAND_PRIORITY:
+ case PROP_RELINQUISH_DEFAULT:
+ case PROP_DESCRIPTION:
+ case PROP_STATE_TEXT:
+// case PROP_PROPERTY_LIST:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ break;
+ default:
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ break;
+ }
+
+ return status;
+}
+
+
+
+
+
+/********************************************/
+/** Functions required for Beremiz plugin **/
+/********************************************/
+
+
+
+void Multistate_Value_Copy_Present_Value_to_Located_Var(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ *(MSV_Descr[i].Located_Var_ptr) = Multistate_Value_Present_Value(MSV_Descr[i].Object_Identifier);
+ }
+}
+
+
+
+void Multistate_Value_Copy_Located_Var_to_Present_Value(void) {
+ unsigned i;
+ for (i = 0; i < MAX_MULTISTATE_VALUES; i++) {
+ // decouple PLC's Located Variable from Bacnet Object's Present Value if Out_Of_Service is true
+ if (MSV_Descr[i].Out_Of_Service)
+ continue;
+
+ // copy the value
+ // Make sure local device does not set Present_Value to a value higher than Number_Of_States
+ /* NOTE: The following comparison (Present_Value > Number_Of_States) is OK as the
+ * MSV_VALUE_NULL is currently set to 0. This means that the local controller
+ * can safely relinquish control by setting the Present_Value to 0.
+ * The comparison would not be safe if for some reason we later change
+ * MSV_VALUE_NULL to a value that may be higher than Number_Of_States.
+ */
+ #if MSV_VALUE_NULL != 0
+ #error Implementation assumes that MSV_VALUE_NULL is set to 0, which is currently not the case.
+ #endif
+ if (*(MSV_Descr[i].Located_Var_ptr) > MSV_Descr[i].Number_Of_States)
+ continue;
+
+ // Everything seems to be OK. Copy the value
+ MSV_Descr[i].Present_Value[BACNET_MAX_PRIORITY-1] = *(MSV_Descr[i].Located_Var_ptr);
+ }
+}
+
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/msv.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,110 @@
+/**************************************************************************
+*
+* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#ifndef MULTISTATE_VALUE_H
+#define MULTISTATE_VALUE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "bacdef.h"
+#include "bacerror.h"
+#include "rp.h"
+#include "wp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* MultiState Values are encoded in unsigned integer
+ * (in BACnet => uint8_t), and can not be 0.
+ * See ASHRAE 135-2016, section 12.20.4
+ */
+#define MULTISTATE_MAX_NUMBER_OF_STATES (255)
+
+
+ typedef struct multistate_value_descr {
+ /* pointer to IEC 61131-3 located variable that is mapped onto this BACnet object */
+ uint8_t *Located_Var_ptr;
+ uint32_t Object_Identifier; /* called object 'Instance' in the source code! */
+ char *Object_Name;
+ char *Description;
+ uint8_t Number_Of_States;
+
+ /* stores the current value */
+ /* one entry per priority value */
+ uint8_t Present_Value[BACNET_MAX_PRIORITY];
+ /* Writable out-of-service allows others to manipulate our Present Value */
+ bool Out_Of_Service;
+ char State_Text[MULTISTATE_MAX_NUMBER_OF_STATES][64];
+ } MULTISTATE_VALUE_DESCR;
+
+
+ void Multistate_Value_Property_Lists(
+ const int **pRequired,
+ const int **pOptional,
+ const int **pProprietary);
+
+ bool Multistate_Value_Valid_Instance(
+ uint32_t object_instance);
+ unsigned Multistate_Value_Count(
+ void);
+ uint32_t Multistate_Value_Index_To_Instance(
+ unsigned index);
+ unsigned Multistate_Value_Instance_To_Index(
+ uint32_t instance);
+
+ int Multistate_Value_Read_Property(
+ BACNET_READ_PROPERTY_DATA * rpdata);
+
+ bool Multistate_Value_Write_Property(
+ BACNET_WRITE_PROPERTY_DATA * wp_data);
+
+ /* optional API */
+ bool Multistate_Value_Object_Name(
+ uint32_t object_instance,
+ BACNET_CHARACTER_STRING * object_name);
+
+ uint32_t Multistate_Value_Present_Value(
+ uint32_t object_instance);
+ bool Multistate_Value_Present_Value_Set(
+ uint32_t object_instance,
+ uint32_t value,
+ uint8_t priority);
+
+ bool Multistate_Value_Out_Of_Service(
+ uint32_t object_instance);
+ void Multistate_Value_Out_Of_Service_Set(
+ uint32_t object_instance,
+ bool value);
+
+ void Multistate_Value_Init(
+ void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/server.c Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,663 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <inttypes.h> // uint32_t, ..., PRIu32, ...
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz pluginh */
+#include "server_%(locstr)s.h"
+#include "address.h"
+#include "bacdef.h"
+#include "handlers.h"
+#include "client.h"
+#include "dlenv.h"
+#include "bacdcode.h"
+#include "npdu.h"
+#include "apdu.h"
+#include "iam.h"
+#include "tsm.h"
+#include "datalink.h"
+#include "dcc.h"
+#include "getevent.h"
+#include "net.h"
+#include "txbuf.h"
+#include "version.h"
+#include "timesync.h"
+
+
+/* A utility function used by most (all?) implementations of BACnet Objects */
+/* Adds to Prop_List all entries in Prop_List_XX that are not
+ * PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PROPERTY_LIST
+ * and returns the number of elements that were added
+ */
+int BACnet_Init_Properties_List(
+ int *Prop_List,
+ const int *Prop_List_XX) {
+
+ unsigned int i = 0;
+ unsigned int j = 0;
+
+ for (j = 0; Prop_List_XX[j] >= 0; j++)
+ // Add any propety, except for the following 4 which should not be included
+ // in the Property_List property array.
+ // (see ASHRAE 135-2016, for example section 12.4.34)
+ if ((Prop_List_XX[j] != PROP_OBJECT_IDENTIFIER) &&
+ (Prop_List_XX[j] != PROP_OBJECT_NAME) &&
+ (Prop_List_XX[j] != PROP_OBJECT_TYPE) &&
+ (Prop_List_XX[j] != PROP_PROPERTY_LIST)) {
+ Prop_List[i] = Prop_List_XX[j];
+ i++;
+ }
+ Prop_List[i] = -1; // marks the end of the list!
+ return i;
+}
+
+
+
+
+
+
+int BACnet_encode_character_string(uint8_t *apdu, const char *str) {
+ BACNET_CHARACTER_STRING char_string;
+ characterstring_init_ansi(&char_string, str);
+ /* FIXME: this might go beyond MAX_APDU length! */
+ return encode_application_character_string(apdu, &char_string);
+}
+
+/* macro that always returns false.
+ * To be used as the 'test_null' parameter to the BACnet_encode_array macro
+ * in situations where we should never encode_null() values.
+ */
+#define retfalse(x) (false)
+
+#define BACnet_encode_array(array, array_len, test_null, encode_function) \
+{ \
+ uint8_t *apdu = NULL; \
+ apdu = rpdata->application_data; \
+ \
+ switch (rpdata->array_index) { \
+ case 0: /* Array element zero is the number of elements in the array */ \
+ apdu_len = encode_application_unsigned(&apdu[0], array_len); \
+ break; \
+ case BACNET_ARRAY_ALL: { \
+ /* if no index was specified, then try to encode the entire list */ \
+ unsigned i = 0; \
+ apdu_len = 0; \
+ for (i = 0; i < array_len; i++) { \
+ /* FIXME: this might go beyond MAX_APDU length! */ \
+ if (!test_null(array[i])) \
+ apdu_len += encode_function (&apdu[apdu_len], array[i]); \
+ else apdu_len += encode_application_null(&apdu[apdu_len]); \
+ /* return error if it does not fit in the APDU */ \
+ if (apdu_len >= MAX_APDU) { \
+ rpdata->error_class = ERROR_CLASS_SERVICES; \
+ rpdata->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; \
+ apdu_len = BACNET_STATUS_ERROR; \
+ break; /* for(;;) */ \
+ } \
+ } \
+ break; \
+ } \
+ default: \
+ if (rpdata->array_index <= array_len) { \
+ if (!test_null(array[rpdata->array_index - 1])) \
+ apdu_len += encode_function(&apdu[0], array[rpdata->array_index - 1]); \
+ else apdu_len += encode_application_null(&apdu[0]); \
+ } else { \
+ rpdata->error_class = ERROR_CLASS_PROPERTY; \
+ rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; \
+ apdu_len = BACNET_STATUS_ERROR; \
+ } \
+ break; \
+ } /* switch() */ \
+}
+
+
+/* include the device object */
+#include "device_%(locstr)s.c"
+#include "ai_%(locstr)s.c"
+#include "ao_%(locstr)s.c"
+#include "av_%(locstr)s.c"
+#include "bi_%(locstr)s.c"
+#include "bo_%(locstr)s.c"
+#include "bv_%(locstr)s.c"
+#include "msi_%(locstr)s.c"
+#include "mso_%(locstr)s.c"
+#include "msv_%(locstr)s.c"
+
+
+/** Buffer used for receiving */
+static uint8_t Rx_Buf[MAX_MPDU] = { 0 };
+
+
+
+
+/*******************************************************/
+/* BACnet Service Handlers taylored to Beremiz plugin */
+/*******************************************************/
+
+static void BACNET_show_date_time(
+ BACNET_DATE * bdate,
+ BACNET_TIME * btime)
+{
+ /* show the date received */
+ fprintf(stderr, "%%u", (unsigned) bdate->year);
+ fprintf(stderr, "/%%u", (unsigned) bdate->month);
+ fprintf(stderr, "/%%u", (unsigned) bdate->day);
+ /* show the time received */
+ fprintf(stderr, " %%02u", (unsigned) btime->hour);
+ fprintf(stderr, ":%%02u", (unsigned) btime->min);
+ fprintf(stderr, ":%%02u", (unsigned) btime->sec);
+ fprintf(stderr, ".%%02u", (unsigned) btime->hundredths);
+ fprintf(stderr, "\r\n");
+}
+
+static time_t __timegm(struct tm *new_time) {
+ time_t sec = mktime(new_time); /* assume new_time is in local time */
+
+ /* sec will be an aproximation of the correct value.
+ * We must now fix this with the current difference
+ * between UTC and localtime.
+ *
+ * WARNING: The following algorithm to determine the current
+ * difference between local time and UTC will not
+ * work if each value (lcl and utc) falls on a different
+ * side of a change to/from DST.
+ * For example, assume a change to DST is made at 12h00
+ * of day X. The following algorithm does not work if:
+ * - lcl falls before 12h00 of day X
+ * - utc falls after 12h00 of day X
+ */
+ struct tm lcl = *localtime(&sec); // lcl will be == new_time
+ struct tm utc = *gmtime (&sec);
+
+ if (lcl.tm_isdst == 1) utc.tm_isdst = 1;
+
+ time_t sec_lcl = mktime(&lcl);
+ time_t sec_utc = mktime(&utc);
+
+ /* difference in seconds between localtime and utc */
+ time_t sec_dif = sec_lcl - sec_utc;
+ return sec + sec_dif;
+}
+
+
+static void BACNET_set_date_time(
+ BACNET_DATE * bdate,
+ BACNET_TIME * btime,
+ int utc /* set to > 0 if date & time in UTC */)
+{
+ struct tm brokendown_time;
+ time_t seconds = 0;
+ struct timespec ts;
+
+ brokendown_time.tm_sec = btime->sec; /* seconds 0..60 */
+ brokendown_time.tm_min = btime->min; /* minutes 0..59 */
+ brokendown_time.tm_hour = btime->hour; /* hours 0..23 */
+ brokendown_time.tm_mday = bdate->day; /* day of the month 1..31 */
+ brokendown_time.tm_mon = bdate->month-1; /* month 0..11 */
+ brokendown_time.tm_year = bdate->year-1900; /* years since 1900 */
+// brokendown_time.tm_wday = ; /* day of the week */
+// brokendown_time.tm_yday = ; /* day in the year */
+ brokendown_time.tm_isdst = -1; /* daylight saving time (-1 => unknown) */
+
+ // Tranform time into format -> 'seconds since epoch'
+ /* WARNING: timegm() is a non standard GNU extension.
+ * If you do not have it on your build system then consider
+ * finding the source code for timegm() (it is LGPL) from GNU
+ * C library and copying it here
+ * (e.g. https://code.woboq.org/userspace/glibc/time/timegm.c.html)
+ * Another alternative is to use the fundion __timegm() above,
+ * which will mostly work but may have errors when the time being
+ * converted is close to the time in the year when changing
+ * to/from DST (daylight saving time)
+ */
+ if (utc > 0) seconds = timegm(&brokendown_time);
+ else seconds = mktime(&brokendown_time);
+
+ ts.tv_sec = seconds;
+ ts.tv_nsec = btime->hundredths*10*1000*1000;
+
+// fprintf(stderr, "clock_settime() s=%%ul, ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+ clock_settime(CLOCK_REALTIME, &ts);
+// clock_gettime(CLOCK_REALTIME, &ts);
+// fprintf(stderr, "clock_gettime() s=%%ul, ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+}
+
+
+
+void BACnet_handler_timesync(
+ uint8_t * service_request,
+ uint16_t service_len,
+ BACNET_ADDRESS * src)
+{
+ int len = 0;
+ BACNET_DATE bdate;
+ BACNET_TIME btime;
+
+ (void) src;
+ (void) service_len;
+ len =
+ timesync_decode_service_request(service_request, service_len, &bdate, &btime);
+ if (len > 0) {
+ fprintf(stderr, "BACnet plugin: Received TimeSyncronization Request -> ");
+ BACNET_show_date_time(&bdate, &btime);
+ /* set the time */
+ BACNET_set_date_time(&bdate, &btime, 0 /* time in local time */);
+ }
+
+ return;
+}
+
+void BACnet_handler_timesync_utc(
+ uint8_t * service_request,
+ uint16_t service_len,
+ BACNET_ADDRESS * src)
+{
+ int len = 0;
+ BACNET_DATE bdate;
+ BACNET_TIME btime;
+
+ (void) src;
+ (void) service_len;
+ len =
+ timesync_decode_service_request(service_request, service_len, &bdate, &btime);
+ if (len > 0) {
+ fprintf(stderr, "BACnet plugin: Received TimeSyncronizationUTC Request -> ");
+ BACNET_show_date_time(&bdate, &btime);
+ /* set the time */
+ BACNET_set_date_time(&bdate, &btime, 1 /* time in UTC */);
+ }
+
+ return;
+}
+
+
+
+/**********************************************/
+/** Initialize the handlers we will utilize. **/
+/**********************************************/
+/*
+ * TLDR: The functions that will create the __Resp.__ messages.
+ *
+ * The service handlers are the functions that will respond to BACnet requests this device receives.
+ * In essence, the service handlers will create and send the Resp. (Response) messages
+ * of the Req. -> Ind. -> Resp. -> Conf. service sequence defined in OSI
+ * (Request, Indication, Response, Confirmation)
+ *
+ */
+static int Init_Service_Handlers(
+ void)
+{
+ /* set the handler for all the services we don't implement */
+ /* It is required to send the proper reject message... */
+ apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
+
+ /* Set the handlers for any unconfirmed services that we support. */
+ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, // DM-DDB-B - Dynamic Device Binding B (Resp.)
+ handler_who_is); // (see ASHRAE 135-2016, section K5.1 and K5.2)
+// apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, // DM-DDB-A - Dynamic Device Binding A (Resp.)
+// handler_i_am_bind); // Responding to I_AM requests is for clients (A)!
+// // (see ASHRAE 135-2016, section K5.1 and K5.2)
+ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, // DM-DOB-B - Dynamic Object Binding B (Resp.)
+ handler_who_has); // (see ASHRAE 135-2016, section K5.3 and K5.4)
+// apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_HAVE, // DM-DOB-A - Dynamic Object Binding A (Resp.)
+// handler_i_have); // Responding to I_HAVE requests is for clients (A)!
+// // (see ASHRAE 135-2016, section K5.3 and K5.4)
+ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, // DM-UTC-B -UTCTimeSynchronization-B (Resp.)
+ BACnet_handler_timesync_utc); // (see ASHRAE 135-2016, section K5.14)
+ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, // DM-TS-B - TimeSynchronization-B (Resp.)
+ BACnet_handler_timesync); // (see ASHRAE 135-2016, section K5.12)
+
+ /* Set the handlers for any confirmed services that we support. */
+ apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, // DS-RP-B - Read Property B (Resp.)
+ handler_read_property); // (see ASHRAE 135-2016, section K1.2)
+// apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, // DS-RPM-B -Read Property Multiple-B (Resp.)
+// handler_read_property_multiple); // (see ASHRAE 135-2016, section K1.4)
+ apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, // DS-WP-B - Write Property B (Resp.)
+ handler_write_property); // (see ASHRAE 135-2016, section K1.8)
+// apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE,// DS-WPM-B -Write Property Multiple B (Resp.)
+// handler_write_property_multiple); // (see ASHRAE 135-2016, section K1.10)
+// apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE,
+// handler_read_range);
+// apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE,
+// handler_reinitialize_device);
+
+// apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV,
+// handler_cov_subscribe);
+// apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_COV_NOTIFICATION,
+// handler_ucov_notification);
+ /* handle communication so we can shutup when asked */
+ apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, // DM-DCC-B - Device Communication Control B
+ handler_device_communication_control); // (see ASHRAE 135-2016, section K5.6)
+// /* handle the data coming back from private requests */
+// apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER,
+// handler_unconfirmed_private_transfer);
+
+ // success
+ return 0;
+}
+
+
+
+static int Init_Network_Interface(
+ const char *interface, // for linux: /dev/eth0, /dev/eth1, /dev/wlan0, ...
+ // for windows: 192.168.0.1 (IP addr. of interface)
+ // NULL => use default!
+ const char *port, // Port the server will listen on. (NULL => use default)
+ const char *apdu_timeout, // (NULL => use default)
+ const char *apdu_retries // (NULL => use default)
+ )
+{
+ char datalink_layer[4];
+
+ strcpy(datalink_layer, "BIP"); // datalink_set() does not accpet const char *, so we copy it...
+// BIP_Debug = true;
+
+ if (port != NULL) {
+ bip_set_port(htons((uint16_t) strtol(port, NULL, 0)));
+ } else {
+ bip_set_port(htons(0xBAC0));
+ }
+
+ if (apdu_timeout != NULL) {
+ apdu_timeout_set((uint16_t) strtol(apdu_timeout, NULL, 0));
+ }
+
+ if (apdu_retries != NULL) {
+ apdu_retries_set((uint8_t) strtol(apdu_retries, NULL, 0));
+ }
+
+ // datalink_init is a pointer that will actually call bip_init()
+ // datalink_init pointer is set by the call datalink_set("BIP")
+ /* NOTE: current implementation of BACnet stack uses the interface
+ * only to determine the server's local IP address and broacast address.
+ * The local IP addr is later used to discard (broadcast) messages
+ * it receives that were sent by itself.
+ * The broadcast IP addr is used for broadcast messages.
+ * WARNING: The socket itself is created to listen on all
+ * available interfaces (INADDR_ANY), so this setting may induce
+ * the user in error as we will accept messages arriving on other
+ * interfaces (if they exist)
+ * (see bip_init() in ports/linux/bip-init.c)
+ * (see bip_****() in src/bip.c)
+ */
+ char *tmp = (char *)malloc(strlen(interface) + 1);
+ if (tmp == NULL) return -1;
+ strncpy(tmp, interface, strlen(interface) + 1);
+ if (!datalink_init(tmp)) {
+ return -1;
+ }
+// #if (MAX_TSM_TRANSACTIONS)
+// pEnv = getenv("BACNET_INVOKE_ID");
+// if (pEnv) {
+// tsm_invokeID_set((uint8_t) strtol(pEnv, NULL, 0));
+// }
+// #endif
+ dlenv_register_as_foreign_device();
+
+ // success
+ return 0;
+}
+
+
+// This mutex blocks execution of __init_%(locstr)s() until initialization is done
+static int init_done = 0;
+static pthread_mutex_t init_done_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t init_done_cond = PTHREAD_COND_INITIALIZER;
+
+/** 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);
+
+ pthread_mutex_lock(&init_done_lock);
+ init_done = 1;
+ pthread_cond_signal(&init_done_cond);
+ pthread_mutex_unlock(&init_done_lock);
+
+ /* Set the password (max 31 chars) for Device Communication Control request. */
+ /* Default in the BACnet stack is hardcoded as "filister" */
+ /* (char *) cast is to remove the cast. The function is incorrectly declared/defined in the BACnet stack! */
+ /* BACnet stack needs to change demo/handler/h_dcc.c and include/handlers.h */
+ handler_dcc_password_set((char *)server_node->comm_control_passwd);
+ /* Set callbacks and configure network interface */
+ res = Init_Service_Handlers();
+ if (res < 0) exit(1);
+ res = Init_Network_Interface(
+ server_node->network_interface, // interface (NULL => use default (eth0))
+ server_node->port_number, // Port number (NULL => use default (0xBAC0))
+ NULL, // apdu_timeout (NULL => use default)
+ NULL // apdu_retries (NULL => use default)
+ );
+ if (res < 0) {
+ fprintf(stderr, "BACnet plugin: error initializing bacnet server node %%s!\n", server_node->location);
+ exit(1); // kill the server thread!
+ }
+ /* BACnet stack correcly configured. Give user some feedback! */
+ struct in_addr my_addr, broadcast_addr;
+ my_addr. s_addr = bip_get_addr();
+ broadcast_addr.s_addr = bip_get_broadcast_addr();
+ printf("BACnet plugin:"
+ " Local IP addr: %%s"
+ ", Broadcast IP addr: %%s"
+ ", Port number: 0x%%04X [%%hu]"
+ ", BACnet Device ID: %%d"
+ ", Max APDU: %%d"
+ ", BACnet Stack Version %%s\n",
+ inet_ntoa(my_addr),
+ inet_ntoa(broadcast_addr),
+ ntohs(bip_get_port()), ntohs(bip_get_port()),
+ Device_Object_Instance_Number(),
+ MAX_APDU,
+ Device_Firmware_Revision()
+ );
+ /* configure the timeout values */
+ last_seconds = time(NULL);
+ /* broadcast an I-Am on startup */
+ Send_I_Am(&Handler_Transmit_Buffer[0]);
+ /* loop forever */
+ for (;;) {
+ /* input */
+ current_seconds = time(NULL);
+
+ /* returns 0 bytes on timeout */
+ pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout);
+
+ /* process */
+ if (pdu_len) {
+ npdu_handler(&src, &Rx_Buf[0], pdu_len);
+ }
+ /* at least one second has passed */
+ elapsed_seconds = (uint32_t) (current_seconds - last_seconds);
+ if (elapsed_seconds) {
+ last_seconds = current_seconds;
+ dcc_timer_seconds(elapsed_seconds);
+ bvlc_maintenance_timer(elapsed_seconds);
+ dlenv_maintenance_timer(elapsed_seconds);
+ elapsed_milliseconds = elapsed_seconds * 1000;
+ tsm_timer_milliseconds(elapsed_milliseconds);
+ }
+ handler_cov_task();
+ /* scan cache address */
+ address_binding_tmr += elapsed_seconds;
+ if (address_binding_tmr >= 60) {
+ address_cache_timer(address_binding_tmr);
+ address_binding_tmr = 0;
+ }
+ }
+
+ /* should never occur!! */
+ return 0;
+}
+
+
+
+
+
+#include <pthread.h>
+
+static void *__bn_server_thread(void *_server_node) {
+ server_node_t *server_node = _server_node;
+
+ // Enable thread cancelation. Enabled is default, but set it anyway to be safe.
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ // bn_server_run() should never return!
+ bn_server_run(server_node);
+ fprintf(stderr, "BACnet plugin: bacnet server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */
+ return NULL;
+}
+
+
+int __cleanup_%(locstr)s ();
+
+int __init_%(locstr)s (int argc, char **argv){
+ int index;
+
+ init_done = 0;
+ /* 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;
+ }
+ }
+
+ pthread_mutex_lock(&init_done_lock);
+ while (!init_done) {
+ pthread_cond_wait(&init_done_cond, &init_done_lock);
+ }
+ pthread_mutex_unlock(&init_done_lock);
+
+ server_node.init_state = 2; // we have created the node and thread
+
+ return 0;
+
+error_exit:
+ __cleanup_%(locstr)s ();
+ return -1;
+}
+
+
+
+
+
+void __publish_%(locstr)s (){
+ Analog_Value_Copy_Located_Var_to_Present_Value();
+ Analog_Input_Copy_Located_Var_to_Present_Value();
+ Analog_Output_Copy_Located_Var_to_Present_Value();
+ Binary_Value_Copy_Located_Var_to_Present_Value();
+ Binary_Input_Copy_Located_Var_to_Present_Value();
+ Binary_Output_Copy_Located_Var_to_Present_Value();
+ Multistate_Value_Copy_Located_Var_to_Present_Value();
+ Multistate_Input_Copy_Located_Var_to_Present_Value();
+ Multistate_Output_Copy_Located_Var_to_Present_Value();
+}
+
+
+
+
+
+void __retrieve_%(locstr)s (){
+ Analog_Value_Copy_Present_Value_to_Located_Var();
+ Analog_Input_Copy_Present_Value_to_Located_Var();
+ Analog_Output_Copy_Present_Value_to_Located_Var();
+ Binary_Value_Copy_Present_Value_to_Located_Var();
+ Binary_Input_Copy_Present_Value_to_Located_Var();
+ Binary_Output_Copy_Present_Value_to_Located_Var();
+ Multistate_Value_Copy_Present_Value_to_Located_Var();
+ Multistate_Input_Copy_Present_Value_to_Located_Var();
+ Multistate_Output_Copy_Present_Value_to_Located_Var();
+}
+
+
+
+
+
+int __cleanup_%(locstr)s (){
+ int index, close;
+ int res = 0;
+
+ /* kill thread and close connections of each modbus server node */
+ close = 0;
+ if (server_node.init_state >= 2) {
+ // thread was launched, so we try to cancel it!
+ close = pthread_cancel(server_node.thread_id);
+ close |= pthread_join (server_node.thread_id, NULL);
+ if (close < 0)
+ fprintf(stderr, "BACnet plugin: Error closing thread for bacnet server %%s\n", server_node.location);
+ }
+ res |= close;
+
+ close = 0;
+ if (server_node.init_state >= 1) {
+ // bacnet server node was created, so we try to close it!
+ // datalink_cleanup is a pointer that will actually call bip_cleanup()
+ // datalink_cleanup pointer is set by the call datalink_set("BIP")
+ datalink_cleanup();
+ }
+ res |= close;
+ server_node.init_state = 0;
+
+ /* bacnet library close */
+ // Nothing to do ???
+
+ return res;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/server.h Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,61 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*********************************************************************/
+
+#ifndef SERVER_H_
+#define SERVER_H_
+
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+
+typedef struct{
+ const char *location;
+ const char *network_interface;
+ const char *port_number;
+ const char *device_name;
+ const char *comm_control_passwd;
+ uint32_t device_id; // device ID is 22 bits long! uint16_t is not enough!
+ int init_state; // store how far along the server's initialization has progressed
+ pthread_t thread_id; // thread handling this server
+ } server_node_t;
+
+
+
+/*initialization following all parameters given by user in application*/
+static server_node_t server_node = {
+ "%(locstr)s",
+ "%(network_interface)s", // interface (NULL => use default (eth0))
+ "%(port_number)s", // Port number (NULL => use default)
+ "%(BACnet_Device_Name)s", // BACnet server's device (object) name
+ "%(BACnet_Comm_Control_Password)s",// BACnet server's device (object) name
+ %(BACnet_Device_ID)s // BACnet server's device (object) ID
+};
+
+
+
+#endif /* SERVER_H_ */
--- a/canfestival/canfestival.py Tue Jun 05 15:29:58 2018 +0200
+++ b/canfestival/canfestival.py Wed Jul 04 14:17:00 2018 +0200
@@ -38,20 +38,16 @@
LOCATION_CONFNODE, \
LOCATION_VAR_MEMORY
-try:
- from nodelist import NodeList
-except ImportError:
- base_folder = paths.AbsParentDir(__file__, 2)
- CanFestivalPath = os.path.join(base_folder, "CanFestival-3")
- sys.path.append(os.path.join(CanFestivalPath, "objdictgen"))
-
- from nodelist import NodeList
-
+base_folder = paths.AbsParentDir(__file__, 2) # noqa
+CanFestivalPath = os.path.join(base_folder, "CanFestival-3") # noqa
+sys.path.append(os.path.join(CanFestivalPath, "objdictgen")) # noqa
+
+# pylint: disable=wrong-import-position
+from nodelist import NodeList
from nodemanager import NodeManager
import gen_cfile
import eds_utils
import canfestival_config as local_canfestival_config # pylint: disable=import-error
-
from commondialogs import CreateNodeDialog
from subindextable import IECTypeConversion, SizeConversion
from canfestival import config_utils
--- a/connectors/PYRO/__init__.py Tue Jun 05 15:29:58 2018 +0200
+++ b/connectors/PYRO/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -139,67 +139,24 @@
confnodesroot.logger.write_error(_("Cannot get PLC status - connection failed.\n"))
return None
+ _special_return_funcs = {
+ "StartPLC": False,
+ "GetTraceVariables": ("Broken", None),
+ "GetPLCstatus": ("Broken", None),
+ "RemoteExec": (-1, "RemoteExec script failed!")
+ }
+
class PyroProxyProxy(object):
"""
A proxy proxy class to handle Beremiz Pyro interface specific behavior.
And to put Pyro exception catcher in between caller and Pyro proxy
"""
- def __init__(self):
- # for safe use in from debug thread, must create a copy
- self.RemotePLCObjectProxyCopy = None
-
- def GetPyroProxy(self):
- """
- This func returns the real Pyro Proxy.
- Use this if you musn't keep reference to it.
- """
- return RemotePLCObjectProxy
-
- def _PyroStartPLC(self, *args, **kwargs):
- """
- confnodesroot._connector.GetPyroProxy() is used
- rather than RemotePLCObjectProxy because
- object is recreated meanwhile,
- so we must not keep ref to it here
- """
- current_status, _log_count = confnodesroot._connector.GetPyroProxy().GetPLCstatus()
- if current_status == "Dirty":
- # Some bad libs with static symbols may polute PLC
- # ask runtime to suicide and come back again
-
- confnodesroot.logger.write(_("Force runtime reload\n"))
- confnodesroot._connector.GetPyroProxy().ForceReload()
- confnodesroot._Disconnect()
- # let remote PLC time to resurect.(freeze app)
- sleep(0.5)
- confnodesroot._Connect()
- self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy())
- return confnodesroot._connector.GetPyroProxy().StartPLC(*args, **kwargs)
- StartPLC = PyroCatcher(_PyroStartPLC, False)
-
- def _PyroGetTraceVariables(self):
- """
- for safe use in from debug thread, must use the copy
- """
- if self.RemotePLCObjectProxyCopy is None:
- self.RemotePLCObjectProxyCopy = copy.copy(confnodesroot._connector.GetPyroProxy())
- return self.RemotePLCObjectProxyCopy.GetTraceVariables()
- GetTraceVariables = PyroCatcher(_PyroGetTraceVariables, ("Broken", None))
-
- def _PyroGetPLCstatus(self):
- return RemotePLCObjectProxy.GetPLCstatus()
- GetPLCstatus = PyroCatcher(_PyroGetPLCstatus, ("Broken", None))
-
- def _PyroRemoteExec(self, script, **kwargs):
- return RemotePLCObjectProxy.RemoteExec(script, **kwargs)
- RemoteExec = PyroCatcher(_PyroRemoteExec, (-1, "RemoteExec script failed!"))
-
def __getattr__(self, attrName):
member = self.__dict__.get(attrName, None)
if member is None:
def my_local_func(*args, **kwargs):
return RemotePLCObjectProxy.__getattr__(attrName)(*args, **kwargs)
- member = PyroCatcher(my_local_func, None)
+ member = PyroCatcher(my_local_func, _special_return_funcs.get(attrName, None))
self.__dict__[attrName] = member
return member
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/PYRO/dialog.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2018: Smarteh
+#
+# See COPYING file for copyrights details.
+
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import wx
+from controls.UriLocationEditor import IConnectorPanel
+from zope.interface import implementer
+
+URITypes = ["LOCAL", "PYRO", "PYROS"]
+
+
+def PYRO_connector_dialog(confnodesroot):
+ [ID_IPTEXT, ID_PORTTEXT] = [wx.NewId() for _init_ctrls in range(2)]
+
+
+ @implementer(IConnectorPanel)
+ class PYROConnectorPanel(wx.Panel):
+ def __init__(self, typeConnector, parrent, *args, **kwargs):
+ self.type = typeConnector
+ self.parrent = parrent
+ wx.Panel.__init__(self, parrent, *args, **kwargs)
+ self._init_ctrls()
+ self._init_sizers()
+ self.uri = None
+
+ def _init_ctrls(self):
+ self.IpText = wx.TextCtrl(parent=self, id=ID_IPTEXT, size = wx.Size(200, -1))
+ self.PortText = wx.TextCtrl(parent=self, id=ID_PORTTEXT, size = wx.Size(200, -1))
+
+ def _init_sizers(self):
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.uriSizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.portSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self.uriSizer.Add(wx.StaticText(self, wx.ID_ANY, "URI host:", size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.uriSizer.AddSpacer((0,0))
+ self.uriSizer.Add(self.IpText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.uriSizer, border=2, flag=wx.ALL)
+
+ self.portSizer.Add(wx.StaticText(self, wx.ID_ANY, "URI port:", size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.portSizer.AddSpacer((0,0))
+ self.portSizer.Add(self.PortText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.portSizer, border=2, flag=wx.ALL)
+
+ self.SetSizer(self.mainSizer)
+
+ def SetURI(self, uri):
+ self.uri = uri
+ uri_list = uri.strip().split(":")
+ length = len(uri_list)
+ if length == 3:
+ self.IpText.SetValue(uri_list[1].strip("/"))
+ self.PortText.SetValue(uri_list[2])
+ elif length == 2:
+ self.IpText.SetValue(uri_list[1].strip("/"))
+
+
+ def GetURI(self):
+ self.uri = self.type+"://"+self.IpText.GetValue()+":"+self.PortText.GetValue()
+ return self.uri
+
+ return PYROConnectorPanel("PYRO", confnodesroot)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/WAMP/dialog.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2018: Smarteh
+#
+# See COPYING file for copyrights details.
+
+
+from __future__ import absolute_import
+from __future__ import print_function
+import wx
+from controls.UriLocationEditor import IConnectorPanel
+from zope.interface import implementer
+
+URITypes = ["WAMP", "WAMPS"]
+
+
+def WAMP_connector_dialog(confnodesroot):
+ [ID_IPTEXT, ID_PORTTEXT, ID_REALMTEXT, ID_WAMPIDTEXT, ID_SECURECHECKBOX] = [wx.NewId() for _init_ctrls in range(5)]
+
+
+ @implementer(IConnectorPanel)
+ class WAMPConnectorPanel(wx.Panel):
+ def __init__(self, typeConnector, parrent, *args, **kwargs):
+ self.type = typeConnector
+ self.parrent = parrent
+ wx.Panel.__init__(self, parrent, *args, **kwargs)
+ self._init_ctrls()
+ self._init_sizers()
+ self.uri = None
+
+ def _init_ctrls(self):
+ self.IpText = wx.TextCtrl(parent=self, id=ID_IPTEXT, size = wx.Size(200, -1))
+ self.PortText = wx.TextCtrl(parent=self, id=ID_PORTTEXT, size = wx.Size(200, -1))
+ self.RealmText = wx.TextCtrl(parent=self, id=ID_REALMTEXT, size = wx.Size(200, -1))
+ self.WAMPIDText = wx.TextCtrl(parent=self, id=ID_WAMPIDTEXT, size = wx.Size(200, -1))
+ self.SecureCheckbox = wx.CheckBox(self, ID_SECURECHECKBOX, _("Is connection secure?"))
+
+ def _init_sizers(self):
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.uriSizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.portSizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.realmSizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.wampIDSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self.uriSizer.Add(wx.StaticText(self, wx.ID_ANY, _("URI host:"), size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.uriSizer.AddSpacer((0,0))
+ self.uriSizer.Add(self.IpText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.uriSizer, border=2, flag=wx.ALL)
+
+ self.portSizer.Add(wx.StaticText(self, wx.ID_ANY, _("URI port:"), size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.portSizer.AddSpacer((0,0))
+ self.portSizer.Add(self.PortText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.portSizer, border=2, flag=wx.ALL)
+
+ self.realmSizer.Add(wx.StaticText(self, wx.ID_ANY, _("Realm:"), size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.realmSizer.AddSpacer((0, 0))
+ self.realmSizer.Add(self.RealmText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.realmSizer, border=2, flag=wx.ALL)
+
+ self.wampIDSizer.Add(wx.StaticText(self, wx.ID_ANY, _("WAMP ID:"), size = wx.Size(70, -1)), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+ self.wampIDSizer.AddSpacer((0, 0))
+ self.wampIDSizer.Add(self.WAMPIDText, proportion=1, flag=wx.ALIGN_RIGHT)
+ self.mainSizer.Add(self.wampIDSizer, border=2, flag=wx.ALL)
+
+ self.mainSizer.Add(self.SecureCheckbox, proportion=1, flag=wx.ALIGN_LEFT)
+
+ self.SetSizer(self.mainSizer)
+
+ def SetURI(self, uri):
+ self.uri = uri
+ uri_list = uri.strip().split(":")
+ length = len(uri_list)
+
+ if length > 0:
+ if uri_list[0] == URITypes[1]:
+ self.SecureCheckbox.SetValue(True)
+
+ if length > 2:
+ self.IpText.SetValue(uri_list[1].strip("/"))
+ wampSett = uri_list[2].split("#")
+ length2 = len(wampSett)
+ if length2 > 0:
+ self.PortText.SetValue(wampSett[0])
+ if length2 > 1:
+ self.RealmText.SetValue(wampSett[1])
+ if length2 > 2:
+ self.WAMPIDText.SetValue(wampSett[2])
+
+ def GetURI(self):
+ if self.IpText.Validate():
+ typeForURI = self.type + "S" if self.SecureCheckbox.GetValue() else self.type
+ self.uri = typeForURI + "://" + self.IpText.GetValue() + ":" + self.PortText.GetValue() + "#" + self.RealmText.GetValue() + "#" + self.WAMPIDText.GetValue()
+ return self.uri
+ else:
+ return ""
+
+ return WAMPConnectorPanel("WAMP", confnodesroot)
--- a/connectors/__init__.py Tue Jun 05 15:29:58 2018 +0200
+++ b/connectors/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -36,6 +36,12 @@
def _GetLocalConnectorClassFactory(name):
return lambda: getattr(__import__(name, globals(), locals()), name + "_connector_factory")
+def _GetLocalConnectorClassDialog(name):
+ return lambda: getattr(__import__(name + '.dialog', globals(), locals(), fromlist=['dialog']), name + "_connector_dialog")
+
+def _GetLocalConnectorURITypes(name):
+ return lambda: getattr(__import__(name + '.dialog', globals(), locals(), fromlist=['dialog']), "URITypes", None)
+
connectors = {name:
_GetLocalConnectorClassFactory(name)
@@ -43,6 +49,12 @@
if (path.isdir(path.join(_base_path, name)) and
not name.startswith("__"))}
+connectors_dialog = {name:
+ {"function":_GetLocalConnectorClassDialog(name), "URITypes": _GetLocalConnectorURITypes(name)}
+ for name in listdir(_base_path)
+ if (path.isdir(path.join(_base_path, name)) and
+ not name.startswith("__"))}
+
def ConnectorFactory(uri, confnodesroot):
"""
@@ -68,3 +80,20 @@
# import module according to uri type
connectorclass = connectors[servicetype]()
return connectorclass(uri, confnodesroot)
+
+def ConnectorDialog(conn_type, confnodesroot):
+ if conn_type not in connectors_dialog:
+ return None
+
+ connectorclass = connectors_dialog[conn_type]["function"]()
+ return connectorclass(confnodesroot)
+
+def GetConnectorFromURI(uri):
+ typeOfConnector = None
+ for conn_type in connectors_dialog:
+ connectorTypes = connectors_dialog[conn_type]["URITypes"]()
+ if connectorTypes and uri in connectorTypes:
+ typeOfConnector = conn_type
+ break
+
+ return typeOfConnector
--- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Wed Jul 04 14:17:00 2018 +0200
@@ -48,7 +48,7 @@
# Canvas height
[SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
-CANVAS_BORDER = (20., 10.) # Border height on at bottom and top of graph
+CANVAS_BORDER = (30., 20.) # Border height on at bottom and top of graph
CANVAS_PADDING = 8.5 # Border inside graph where no label is drawn
VALUE_LABEL_HEIGHT = 17. # Height of variable label in graph
AXES_LABEL_HEIGHT = 12.75 # Height of variable value in graph
--- a/controls/DebugVariablePanel/DebugVariableItem.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/DebugVariablePanel/DebugVariableItem.py Wed Jul 04 14:17:00 2018 +0200
@@ -24,6 +24,7 @@
from __future__ import absolute_import
+from datetime import timedelta
import binascii
import numpy
from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR
@@ -219,18 +220,20 @@
else:
self.Data = None
-
+ self.MinValue = None
+ self.MaxValue = None
# Init variable value
self.Value = ""
def IsNumVariable(self):
"""
Return if variable data type is numeric. String variables are
- considered as numeric (string CRC)
+ considered as numeric (string CRC). Time variables are considered
+ as number of seconds
@return: True if data type is numeric
"""
return (self.Parent.IsNumType(self.VariableType) or
- self.VariableType in ["STRING", "WSTRING"])
+ self.VariableType in ["STRING", "WSTRING", "TIME", "TOD", "DT", "DATE"])
def NewValues(self, ticks, values):
"""
@@ -253,10 +256,15 @@
# Translate forced flag to float for storing in Data table
forced_value = float(forced)
- # String data value is CRC
- num_value = (binascii.crc32(value) & STRING_CRC_MASK
- if self.VariableType in ["STRING", "WSTRING"]
- else float(value))
+ if self.VariableType in ["STRING", "WSTRING"]:
+ # String data value is CRC
+ num_value = (binascii.crc32(value) & STRING_CRC_MASK)
+ elif self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
+ # Numeric value of time type variables
+ # is represented in seconds
+ num_value = float(value.total_seconds())
+ else:
+ num_value = float(value)
# Update variable range values
self.MinValue = (min(self.MinValue, num_value)
@@ -341,6 +349,9 @@
if self.VariableType in ["STRING", "WSTRING"] \
else self.Data[idx, 1:3]
+ if self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
+ value = timedelta(seconds=value)
+
# Get raw value if asked
if not raw:
value = TYPE_TRANSLATOR.get(
--- a/controls/DebugVariablePanel/DebugVariablePanel.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/DebugVariablePanel/DebugVariablePanel.py Wed Jul 04 14:17:00 2018 +0200
@@ -237,7 +237,7 @@
default_range_idx = 0
for idx, (text, _value) in enumerate(RANGE_VALUES):
self.CanvasRange.Append(text)
- if text == "1s":
+ if _value == 1000000000:
default_range_idx = idx
self.CanvasRange.SetSelection(default_range_idx)
--- a/controls/LogViewer.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/LogViewer.py Wed Jul 04 14:17:00 2018 +0200
@@ -396,7 +396,7 @@
self.HasNewData = False
def SetLogSource(self, log_source):
- self.LogSource = proxy(log_source) if log_source else None
+ self.LogSource = proxy(log_source) if log_source is not None else None
self.CleanButton.Enable(self.LogSource is not None)
if log_source is not None:
self.ResetLogMessages()
--- a/controls/ProjectPropertiesPanel.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/ProjectPropertiesPanel.py Wed Jul 04 14:17:00 2018 +0200
@@ -27,6 +27,8 @@
import wx
from wx.lib.scrolledpanel import ScrolledPanel
+from xmlclass.xmlclass import URI_model
+
# -------------------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------------------
@@ -294,8 +296,16 @@
if self.Controller is not None and self.Values is not None:
old_value = self.Values.get(name)
new_value = textctrl.GetValue()
- if name not in REQUIRED_PARAMS and new_value == "":
+ if name in REQUIRED_PARAMS and new_value == "":
new_value = None
+ if name == 'companyURL':
+ if not URI_model.match(new_value):
+ new_value = None
+ dialog = wx.MessageDialog(self, _('Invalid URL!\n'
+ 'Please enter correct URL address.'),
+ _("Error"), wx.OK | wx.ICON_ERROR)
+ dialog.ShowModal()
+ dialog.Destroy()
if old_value != new_value:
self.Controller.SetProjectProperties(properties={name: new_value})
self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/UriLocationEditor.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,110 @@
+import wx
+from zope.interface import Interface, Attribute
+from zope.interface.verify import verifyObject
+from connectors import connectors_dialog, ConnectorDialog, GetConnectorFromURI
+
+
+[ID_URIWIZARDDIALOG,ID_URITYPECHOICE] = [wx.NewId() for _init_ctrls in range(2)]
+
+class IConnectorPanel(Interface):
+ """This is interface for panel of seperate connector type"""
+ uri = Attribute("""uri of connections""")
+ type = Attribute("""type of connector""")
+
+ def SetURI(uri):
+ """methode for set uri"""
+
+ def GetURI():
+ """metohde for get uri"""
+
+
+class UriLocationEditor(wx.Dialog):
+ def _init_ctrls(self, parent):
+ wx.Dialog.__init__(self, id=ID_URIWIZARDDIALOG,
+ name='UriLocationEditor', parent=parent,
+ title='Uri location')
+ self.UriTypeChoice = wx.Choice(parent=self, id=ID_URIWIZARDDIALOG, choices = self.URITYPES)
+ self.UriTypeChoice.SetSelection(0)
+ self.Bind(wx.EVT_CHOICE, self.OnTypeChoice, self.UriTypeChoice)
+ self.PanelSizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL)
+
+ def _init_sizers(self):
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+ typeSizer = wx.BoxSizer(wx.HORIZONTAL)
+ typeSizer.Add(wx.StaticText(self,wx.ID_ANY,"URI type:"), border=5, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL)
+ typeSizer.Add(self.UriTypeChoice, border=5, flag=wx.ALL)
+ self.mainSizer.Add(typeSizer)
+
+ self.mainSizer.Add(self.PanelSizer, border=5, flag=wx.ALL)
+ self.mainSizer.Add(self.ButtonSizer, border=5, flag=wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL)
+ self.SetSizer(self.mainSizer)
+
+ def __init__(self, parent, uri):
+ self.URITYPES = ["- Select URI type -"]
+ for connector_type, connector_function in connectors_dialog.iteritems():
+ try:
+ connector_function['function']()
+ self.URITYPES.append(connector_type)
+ except Exception as e:
+ pass
+
+ self.selected = None
+ self.parrent = parent
+ self.logger = self.parrent.CTR.logger
+ self._init_ctrls(parent)
+ self._init_sizers()
+ self.SetURI(uri)
+ self.CenterOnParent()
+
+ def OnTypeChoice(self, event):
+ self._removePanelType()
+ index = event.GetSelection()
+ if index > 0:
+ self.selected = event.GetString()
+ self.panelType = self._getConnectorDialog(self.selected)
+ if self.panelType:
+ self.PanelSizer.Add(self.panelType)
+ self.mainSizer.Layout()
+ self.Fit()
+ self.panelType.Refresh()
+
+ def SetURI(self, uri):
+ self._removePanelType()
+ uri_list = uri.strip().split(":")
+ if uri_list:
+ uri_type = uri_list[0].upper()
+ type = GetConnectorFromURI(uri_type)
+ if type:
+ self.selected = type
+ self.UriTypeChoice.SetStringSelection(self.selected)
+ self.panelType = self._getConnectorDialog(self.selected)
+ if self.panelType:
+ self.panelType.SetURI(uri)
+ self.PanelSizer.Add(self.panelType)
+ self.PanelSizer.Layout()
+ self.mainSizer.Layout()
+ self.Fit()
+ self.panelType.Refresh()
+
+ def GetURI(self):
+ if not self.selected or not self.panelType:
+ return ""
+ else:
+ return self.panelType.GetURI()
+
+ def _removePanelType(self):
+ for i in range(self.PanelSizer.GetItemCount()):
+ item = self.PanelSizer.GetItem(i)
+ item.DeleteWindows()
+ self.PanelSizer.Remove(i)
+ self.Fit()
+ self.PanelSizer.Layout()
+
+ def _getConnectorDialog(self, connectorType):
+ connector = ConnectorDialog(connectorType, self)
+ if connector and IConnectorPanel.providedBy(connector):
+ if verifyObject(IConnectorPanel, connector):
+ return connector
+ else:
+ return None
--- a/controls/VariablePanel.py Tue Jun 05 15:29:58 2018 +0200
+++ b/controls/VariablePanel.py Wed Jul 04 14:17:00 2018 +0200
@@ -105,7 +105,6 @@
}
LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
-VARIABLE_NAME_SUFFIX_MODEL = re.compile("([0-9]*)$")
# -------------------------------------------------------------------------------
@@ -514,7 +513,7 @@
self.FilterChoices = []
self.FilterChoiceTransfer = GetFilterChoiceTransfer()
- self.DefaultValue = _VariableInfos("", "", "", "", "", True, "", DefaultType, ([], []), 0)
+ self.DefaultValue = _VariableInfos("LocalVar0", "", "", "", "", True, "", DefaultType, ([], []), 0)
if element_type in ["config", "resource"]:
self.DefaultTypes = {"All": "Global"}
@@ -590,36 +589,16 @@
self.VariablesGrid.SetEditable(not self.Debug)
def _AddVariable(new_row):
+ row_content = self.DefaultValue.copy()
if new_row > 0:
- row_content = self.Values[new_row - 1].copy()
-
- result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content.Name)
- if result is not None:
- name = row_content.Name[:result.start(1)]
- suffix = result.group(1)
- if suffix != "":
- start_idx = int(suffix)
- else:
- start_idx = 0
- else:
- name = row_content.Name
- start_idx = 0
- else:
- row_content = None
- start_idx = 0
- name = "LocalVar"
-
- if row_content is not None and row_content.Edit:
- row_content = self.Values[new_row - 1].copy()
- else:
- row_content = self.DefaultValue.copy()
- if self.Filter in self.DefaultTypes:
- row_content.Class = self.DefaultTypes[self.Filter]
- else:
- row_content.Class = self.Filter
-
- row_content.Name = self.Controler.GenerateNewName(
- self.TagName, None, name + "%d", start_idx)
+ # doesn't copy values of previous var if it's non-editable (like a FB)
+ if self.Values[new_row-1].Edit:
+ row_content = self.Values[new_row-1].copy()
+ old_name = self.Values[new_row-1].Name
+ row_content.Name = self.Controler.GenerateNewName(
+ self.TagName, old_name, old_name+'%d')
+ if not row_content.Class:
+ row_content.Class = self.DefaultTypes.get(self.Filter, self.Filter)
if self.Filter == "All" and len(self.Values) > 0:
self.Values.insert(new_row, row_content)
--- a/editors/CodeFileEditor.py Tue Jun 05 15:29:58 2018 +0200
+++ b/editors/CodeFileEditor.py Wed Jul 04 14:17:00 2018 +0200
@@ -35,7 +35,6 @@
from plcopen.structures import TestIdentifier, IEC_KEYWORDS, DefaultType
from controls import CustomGrid, CustomTable
from controls.CustomStyledTextCtrl import CustomStyledTextCtrl, faces, GetCursorPos, NAVIGATION_KEYS
-from controls.VariablePanel import VARIABLE_NAME_SUFFIX_MODEL
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
from util.BitmapLibrary import GetBitmap
from util.TranslationCatalogs import NoTranslate
@@ -674,7 +673,7 @@
self.Controler = controler
self.VariablesDefaultValue = {
- "Name": "",
+ "Name": "LocalVar0",
"Type": DefaultType,
"Initial": "",
"Description": "",
@@ -694,19 +693,9 @@
def _AddVariable(new_row):
if new_row > 0:
row_content = self.Table.data[new_row - 1].copy()
- result = VARIABLE_NAME_SUFFIX_MODEL.search(row_content["Name"])
- if result is not None:
- name = row_content["Name"][:result.start(1)]
- suffix = result.group(1)
- if suffix != "":
- start_idx = int(suffix)
- else:
- start_idx = 0
- else:
- name = row_content["Name"]
- start_idx = 0
- row_content["Name"] = self.Controler.GenerateNewName(
- name + "%d", start_idx)
+ old_name = row_content['Name']
+ row_content['Name'] =\
+ self.Controler.GenerateNewName(old_name, old_name+'%d')
else:
row_content = self.VariablesDefaultValue.copy()
self.Table.InsertRow(new_row, row_content)
--- a/editors/ConfTreeNodeEditor.py Tue Jun 05 15:29:58 2018 +0200
+++ b/editors/ConfTreeNodeEditor.py Wed Jul 04 14:17:00 2018 +0200
@@ -33,7 +33,7 @@
from IDEFrame import TITLE, FILEMENU, PROJECTTREE, PAGETITLES
-from controls import TextCtrlAutoComplete
+from controls import TextCtrlAutoComplete, UriLocationEditor
from dialogs import BrowseValuesLibraryDialog
from util.BitmapLibrary import GetBitmap
@@ -338,6 +338,28 @@
msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
return msizer
+ def UriOptions(self, event):
+ CTR = self.ParentWindow.CTR
+ CTR_BeremizRoot = CTR.BeremizRoot
+ CTR_AppFrame = CTR.AppFrame
+
+ # Get connector uri
+ uri = CTR_BeremizRoot.getURI_location().strip()
+ dialog = UriLocationEditor.UriLocationEditor(CTR_AppFrame, uri)
+
+ if dialog.ShowModal() == wx.ID_OK:
+ CTR_BeremizRoot.setURI_location(dialog.GetURI())
+ if CTR._View is not None:
+ CTR._View.RefreshView()
+ if CTR_AppFrame is not None:
+ CTR_AppFrame.RefreshTitle()
+ CTR_AppFrame.RefreshFileMenu()
+ CTR_AppFrame.RefreshEditMenu()
+ CTR_AppFrame.RefreshPageTitles()
+
+ dialog.Destroy()
+
+
def GenerateSizerElements(self, sizer, elements, path, clean=True):
if clean:
sizer.Clear(True)
@@ -484,11 +506,26 @@
element_path=element_path,
size=wx.Size(300, -1))
- boxsizer.AddWindow(textctrl)
+ if element_infos["name"] == "URI_location":
+ uriSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
+ uriSizer.AddGrowableCol(0)
+ uriSizer.AddGrowableRow(0)
+
+ self.EditButton = wx.Button(self.ParamsEditor, label='...', size=wx.Size(30, -1))
+ self.Bind(wx.EVT_BUTTON, self.UriOptions, self.EditButton)
+
+ uriSizer.AddWindow(textctrl, flag=wx.GROW)
+ uriSizer.AddWindow(self.EditButton, flag=wx.GROW)
+
+ boxsizer.AddWindow(uriSizer)
+ else:
+ boxsizer.AddWindow(textctrl)
+
if element_infos["value"] is not None:
textctrl.ChangeValue(str(element_infos["value"]))
callback = self.GetTextCtrlCallBackFunction(textctrl, element_path)
textctrl.Bind(wx.EVT_TEXT_ENTER, callback)
+ textctrl.Bind(wx.EVT_TEXT, callback)
textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
first = False
sizer.Layout()
--- a/editors/ResourceEditor.py Tue Jun 05 15:29:58 2018 +0200
+++ b/editors/ResourceEditor.py Wed Jul 04 14:17:00 2018 +0200
@@ -303,7 +303,8 @@
self.RefreshHighlightsTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
- self.TasksDefaultValue = {"Name": "", "Triggering": "", "Single": "", "Interval": "", "Priority": 0}
+ self.TasksDefaultValue = {"Name": "task0", "Triggering": "Cyclic",
+ "Single": "", "Interval": "T#20ms", "Priority": 0}
self.TasksTable = ResourceTable(self, [], GetTasksTableColnames())
self.TasksTable.SetColAlignements([wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT])
self.TasksTable.SetColSizes([200, 100, 100, 150, 100])
@@ -314,7 +315,15 @@
"Down": self.DownTaskButton})
def _AddTask(new_row):
- self.TasksTable.InsertRow(new_row, self.TasksDefaultValue.copy())
+ if new_row > 0:
+ row_content = self.TasksTable.data[new_row-1].copy()
+ old_name = row_content['Name']
+ row_content['Name'] =\
+ self.Controler.GenerateNewName(self.TagName, old_name, old_name+'%d')
+ else:
+ row_content = self.TasksDefaultValue.copy()
+
+ self.TasksTable.InsertRow(new_row, row_content)
self.RefreshModel()
self.RefreshView()
return new_row
@@ -338,7 +347,7 @@
self.TasksTable.ResetView(self.TasksGrid)
self.TasksGrid.RefreshButtons()
- self.InstancesDefaultValue = {"Name": "", "Type": "", "Task": ""}
+ self.InstancesDefaultValue = {"Name": "instance0", "Type": "", "Task": ""}
self.InstancesTable = ResourceTable(self, [], GetInstancesTableColnames())
self.InstancesTable.SetColAlignements([wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT])
self.InstancesTable.SetColSizes([200, 150, 150])
@@ -349,7 +358,15 @@
"Down": self.DownInstanceButton})
def _AddInstance(new_row):
- self.InstancesTable.InsertRow(new_row, self.InstancesDefaultValue.copy())
+ if new_row > 0:
+ row_content = self.InstancesTable.data[new_row - 1].copy()
+ old_name = row_content['Name']
+ row_content['Name'] =\
+ self.Controler.GenerateNewName(self.TagName, old_name, old_name+'%d')
+ else:
+ row_content = self.InstancesDefaultValue.copy()
+
+ self.InstancesTable.InsertRow(new_row, row_content)
self.RefreshModel()
self.RefreshView()
return new_row
--- a/features.py Tue Jun 05 15:29:58 2018 +0200
+++ b/features.py Wed Jul 04 14:17: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'),
--- a/modbus/__init__.py Tue Jun 05 15:29:58 2018 +0200
+++ b/modbus/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -8,7 +8,7 @@
#
# 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 3 of the License, or
+# 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,
--- a/modbus/mb_runtime.c Tue Jun 05 15:29:58 2018 +0200
+++ b/modbus/mb_runtime.c Wed Jul 04 14:17:00 2018 +0200
@@ -7,7 +7,7 @@
*
* This Modbus library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
+ * 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
@@ -492,12 +492,13 @@
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
/*just do the output requests */
if (client_requests[index].req_type == req_output){
- pthread_mutex_lock(&(client_requests[index].coms_buf_mutex));
- // copy from plcv_buffer to coms_buffer
- memcpy((void *)client_requests[index].coms_buffer /* destination */,
- (void *)client_requests[index].plcv_buffer /* source */,
- REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
- pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
+ if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){
+ // copy from plcv_buffer to coms_buffer
+ memcpy((void *)client_requests[index].coms_buffer /* destination */,
+ (void *)client_requests[index].plcv_buffer /* source */,
+ REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
+ pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
+ }
}
}
}
@@ -512,12 +513,13 @@
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
/*just do the input requests */
if (client_requests[index].req_type == req_input){
- pthread_mutex_lock(&(client_requests[index].coms_buf_mutex));
- // copy from coms_buffer to plcv_buffer
- memcpy((void *)client_requests[index].plcv_buffer /* destination */,
- (void *)client_requests[index].coms_buffer /* source */,
- REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
- pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
+ if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){
+ // copy from coms_buffer to plcv_buffer
+ memcpy((void *)client_requests[index].plcv_buffer /* destination */,
+ (void *)client_requests[index].coms_buffer /* source */,
+ REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
+ pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
+ }
}
}
}
--- a/modbus/mb_runtime.h Tue Jun 05 15:29:58 2018 +0200
+++ b/modbus/mb_runtime.h Wed Jul 04 14:17:00 2018 +0200
@@ -7,7 +7,7 @@
*
* This Modbus library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
+ * 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
--- a/modbus/mb_utils.py Tue Jun 05 15:29:58 2018 +0200
+++ b/modbus/mb_utils.py Wed Jul 04 14:17:00 2018 +0200
@@ -8,7 +8,7 @@
#
# 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 3 of the License, or
+# 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,
--- a/modbus/modbus.py Tue Jun 05 15:29:58 2018 +0200
+++ b/modbus/modbus.py Wed Jul 04 14:17:00 2018 +0200
@@ -8,7 +8,7 @@
#
# 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 3 of the License, or
+# 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,
--- a/plcopen/BlockInstanceCollector.py Tue Jun 05 15:29:58 2018 +0200
+++ b/plcopen/BlockInstanceCollector.py Wed Jul 04 14:17:00 2018 +0200
@@ -4,8 +4,8 @@
# See COPYING file for copyrights details.
from __future__ import absolute_import
+from collections import OrderedDict, namedtuple
from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
-from collections import OrderedDict, namedtuple
# -------------------------------------------------------------------------------
# Helpers object for generating pou block instances list
--- a/plcopen/plcopen.py Tue Jun 05 15:29:58 2018 +0200
+++ b/plcopen/plcopen.py Wed Jul 04 14:17:00 2018 +0200
@@ -152,12 +152,12 @@
test_result = []
result = criteria["pattern"].search(text)
while result is not None:
- prev_pos = result.endpos
+ prev_pos = result.span()[1]
start = TextLenInRowColumn(text[:result.start()])
end = TextLenInRowColumn(text[:result.end() - 1])
test_result.append((start, end, "\n".join(lines[start[0]:end[0] + 1])))
result = criteria["pattern"].search(text, result.end())
- if result is not None and prev_pos == result.endpos:
+ if result is not None and prev_pos == result.end():
break
return test_result
@@ -441,7 +441,7 @@
"authorName": contentheader_obj.setauthor,
"pageSize": lambda v: contentheader_obj.setpageSize(*v),
"scaling": contentheader_obj.setscaling}.get(attr)
- if func is not None:
+ if func is not None and value is not None:
func(value)
elif attr in ["modificationDateTime", "organization", "language"]:
setattr(contentheader_obj, attr, value)
--- a/runtime/NevowServer.py Tue Jun 05 15:29:58 2018 +0200
+++ b/runtime/NevowServer.py Wed Jul 04 14:17:00 2018 +0200
@@ -26,10 +26,18 @@
from __future__ import absolute_import
from __future__ import print_function
import os
-from nevow import appserver, inevow, tags, loaders, athena
+from zope.interface import implements
+from nevow import appserver, inevow, tags, loaders, athena, url, rend
from nevow.page import renderer
+from formless import annotate
+from formless import webform
+from formless import configurable
from twisted.internet import reactor
+
import util.paths as paths
+from runtime.loglevels import LogLevels, LogLevelsDict
+
+PAGE_TITLE = 'Beremiz Runtime Web Interface'
xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
@@ -37,6 +45,7 @@
'''
WorkingDir = None
+_PySrv = None
class PLCHMI(athena.LiveElement):
@@ -49,7 +58,6 @@
def HMIinitialisation(self):
self.HMIinitialised(None)
-
class DefaultPLCStartedHMI(PLCHMI):
docFactory = loaders.stan(
tags.div(render=tags.directive('liveElement'))[
@@ -119,19 +127,104 @@
for child in self.liveFragmentChildren[:]:
child.detach()
+class ConfigurableBindings(configurable.Configurable):
+
+ def __init__(self):
+ configurable.Configurable.__init__(self, None)
+ self.bindingsNames = []
+
+ def getBindingNames(self, ctx):
+ return self.bindingsNames
+
+ def addExtension(self, name, desc, fields, btnlabel, callback):
+ def _bind(ctx):
+ return annotate.MethodBinding(
+ 'action_'+name,
+ annotate.Method(arguments=[
+ annotate.Argument(name, fieldtype)
+ for fieldname,fieldtype in fields],
+ label = desc),
+ action = btnlabel))
+ setattr(self, 'bind_'+name, _bind)
+
+ setattr(self, 'action_'+name, callback)
+
+ self.bindingsNames.append(name)
+
+ConfigurableSettings = ConfigurableBindings()
+
+class ISettings(annotate.TypedInterface):
+ def sendLogMessage(
+ ctx = annotate.Context(),
+ level = annotate.Choice(LogLevels,
+ required=True,
+ label=_("Log message level")),
+ message = annotate.String(label=_("Message text"))):
+ pass
+ sendLogMessage = annotate.autocallable(sendLogMessage,
+ label=_("Send a message to the log"),
+ action=_("Send"))
+
+
+class SettingsPage(rend.Page):
+ # We deserve a slash
+ addSlash = True
+
+ # This makes webform_css url answer some default CSS
+ child_webform_css = webform.defaultCSS
+
+ implements(ISettings)
+
+
+ docFactory = loaders.stan([tags.html[
+ tags.head[
+ tags.title[_("Beremiz Runtime Settings")],
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webform_css"))
+ ],
+ tags.body[
+ tags.h1["Runtime settings:"],
+ webform.renderForms('staticSettings'),
+ tags.h2["Extensions settings:"],
+ webform.renderForms('dynamicSettings'),
+ ]]])
+
+ def __init__(self):
+ rend.Page.__init__(self)
+
+ def configurable_staticSettings(self, ctx):
+ return configurable.TypedInterfaceConfigurable(self)
+
+ def configurable_dynamicSettings(self, ctx):
+ return ConfigurableSettings
+
+ def sendLogMessage(self, level, message, **kwargs):
+ level = LogLevelsDict[level]
+ if _PySrv.plcobj is not None:
+ _PySrv.plcobj.LogMessage(level, "Web form log message: " + message )
+
class WebInterface(athena.LivePage):
docFactory = loaders.stan([tags.raw(xhtml_header),
tags.html(xmlns="http://www.w3.org/1999/xhtml")[
- tags.head(render=tags.directive('liveglue')),
+ tags.head(render=tags.directive('liveglue'))[
+ tags.title[PAGE_TITLE],
+ tags.link(rel='stylesheet',
+ type='text/css',
+ href=url.here.child("webform_css"))
+ ],
tags.body[
tags.div[
- tags.div(render=tags.directive("MainPage"))
+ tags.div(render=tags.directive("MainPage")),
]]]])
MainPage = MainPage()
PLCHMI = PLCHMI
+ def child_settings(self, context):
+ return SettingsPage()
+
def __init__(self, plcState=False, *a, **kw):
super(WebInterface, self).__init__(*a, **kw)
self.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile(__file__, 'webinterface.js')
@@ -184,6 +277,7 @@
# print "We will be called back when the client disconnects"
+
def RegisterWebsite(port):
website = WebInterface()
site = appserver.NevowSite(website)
@@ -209,3 +303,9 @@
def website_statuslistener_factory(site):
return statuslistener(site).listen
+
+
+def SetServer(pysrv):
+ global _PySrv
+ _PySrv = pysrv
+
--- a/runtime/PLCObject.py Tue Jun 05 15:29:58 2018 +0200
+++ b/runtime/PLCObject.py Wed Jul 04 14:17:00 2018 +0200
@@ -23,7 +23,8 @@
from __future__ import absolute_import
-from threading import Timer, Thread, Lock, Semaphore, Event
+import thread
+from threading import Thread, Lock, Semaphore, Event, Condition
import ctypes
import os
import sys
@@ -60,31 +61,139 @@
sys.stdout.flush()
+class job(object):
+ """
+ job to be executed by a worker
+ """
+ def __init__(self, call, *args, **kwargs):
+ self.job = (call, args, kwargs)
+ self.result = None
+ self.success = False
+ self.exc_info = None
+
+ def do(self):
+ """
+ do the job by executing the call, and deal with exceptions
+ """
+ try:
+ call, args, kwargs = self.job
+ self.result = call(*args, **kwargs)
+ self.success = True
+ except Exception:
+ self.success = False
+ self.exc_info = sys.exc_info()
+
+
+class worker(object):
+ """
+ serialize main thread load/unload of PLC shared objects
+ """
+ def __init__(self):
+ # Only one job at a time
+ self._finish = False
+ self._threadID = None
+ self.mutex = Lock()
+ self.todo = Condition(self.mutex)
+ self.done = Condition(self.mutex)
+ self.free = Condition(self.mutex)
+ self.job = None
+
+ def runloop(self, *args, **kwargs):
+ """
+ meant to be called by worker thread (blocking)
+ """
+ self._threadID = thread.get_ident()
+ if args or kwargs:
+ job(*args, **kwargs).do()
+ # result is ignored
+ self.mutex.acquire()
+ while not self._finish:
+ self.todo.wait()
+ if self.job is not None:
+ self.job.do()
+ self.done.notify()
+ else:
+ self.free.notify()
+ self.mutex.release()
+
+ def call(self, *args, **kwargs):
+ """
+ creates a job, execute it in worker thread, and deliver result.
+ if job execution raise exception, re-raise same exception
+ meant to be called by non-worker threads, but this is accepted.
+ blocking until job done
+ """
+
+ _job = job(*args, **kwargs)
+
+ if self._threadID == thread.get_ident() or self._threadID is None:
+ # if caller is worker thread execute immediately
+ _job.do()
+ else:
+ # otherwise notify and wait for completion
+ self.mutex.acquire()
+
+ while self.job is not None:
+ self.free.wait()
+
+ self.job = _job
+ self.todo.notify()
+ self.done.wait()
+ _job = self.job
+ self.job = None
+ self.mutex.release()
+
+ if _job.success:
+ return _job.result
+ else:
+ raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2]
+
+ def quit(self):
+ """
+ unblocks main thread, and terminate execution of runloop()
+ """
+ # mark queue
+ self._finish = True
+ self.mutex.acquire()
+ self.job = None
+ self.todo.notify()
+ self.mutex.release()
+
+
+MainWorker = worker()
+
+
+def RunInMain(func):
+ def func_wrapper(*args, **kwargs):
+ return MainWorker.call(func, *args, **kwargs)
+ return func_wrapper
+
+
class PLCObject(pyro.ObjBase):
- def __init__(self, workingdir, daemon, argv, statuschange, evaluator, pyruntimevars):
+ def __init__(self, server):
pyro.ObjBase.__init__(self)
- self.evaluator = evaluator
- self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
- self.workingdir = workingdir
+ self.evaluator = server.evaluator
+ self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec...
+ self.workingdir = server.workdir
self.PLCStatus = "Empty"
self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
- self._FreePLC()
- self.daemon = daemon
- self.statuschange = statuschange
+ self._InitPLCStubCalls()
+ self.daemon = server.daemon
+ self.statuschange = server.statuschange
self.hmi_frame = None
- self.pyruntimevars = pyruntimevars
+ self.pyruntimevars = server.pyruntimevars
self._loading_error = None
self.python_runtime_vars = None
self.TraceThread = None
self.TraceLock = Lock()
- self.TraceWakeup = Event()
self.Traces = []
+ # First task of worker -> no @RunInMain
def AutoLoad(self):
- # Get the last transfered PLC if connector must be restart
+ # Get the last transfered PLC
try:
self.CurrentPLCFilename = open(
self._GetMD5FileName(),
@@ -100,6 +209,7 @@
for callee in self.statuschange:
callee(self.PLCStatus)
+ @RunInMain
def LogMessage(self, *args):
if len(args) == 2:
level, msg = args
@@ -111,16 +221,19 @@
return self._LogMessage(level, msg, len(msg))
return None
+ @RunInMain
def ResetLogCount(self):
if self._ResetLogCount is not None:
self._ResetLogCount()
+ # used internaly
def GetLogCount(self, level):
if self._GetLogCount is not None:
return int(self._GetLogCount(level))
elif self._loading_error is not None and level == 0:
return 1
+ @RunInMain
def GetLogMessage(self, level, msgid):
tick = ctypes.c_uint32()
tv_sec = ctypes.c_uint32()
@@ -145,12 +258,13 @@
def _GetLibFileName(self):
return os.path.join(self.workingdir, self.CurrentPLCFilename)
- def LoadPLC(self):
+ def _LoadPLC(self):
"""
Load PLC library
Declare all functions, arguments and return values
"""
md5 = open(self._GetMD5FileName(), "r").read()
+ self.PLClibraryLock.acquire()
try:
self._PLClibraryHandle = dlopen(self._GetLibFileName())
self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
@@ -227,25 +341,34 @@
self._loading_error = None
- self.PythonRuntimeInit()
-
- return True
except Exception:
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
return False
-
+ finally:
+ self.PLClibraryLock.release()
+
+ return True
+
+ @RunInMain
+ def LoadPLC(self):
+ res = self._LoadPLC()
+ if res:
+ self.PythonRuntimeInit()
+ else:
+ self._FreePLC()
+
+ return res
+
+ @RunInMain
def UnLoadPLC(self):
self.PythonRuntimeCleanup()
self._FreePLC()
- def _FreePLC(self):
- """
- Unload PLC library.
- This is also called by __init__ to create dummy C func proxies
- """
- self.PLClibraryLock.acquire()
- # Forget all refs to library
+ def _InitPLCStubCalls(self):
+ """
+ create dummy C func proxies
+ """
self._startPLC = lambda x, y: None
self._stopPLC = lambda: None
self._ResetDebugVariables = lambda: None
@@ -259,13 +382,26 @@
self._GetLogCount = None
self._LogMessage = None
self._GetLogMessage = None
+ self._PLClibraryHandle = None
self.PLClibraryHandle = None
- # Unload library explicitely
- if getattr(self, "_PLClibraryHandle", None) is not None:
- dlclose(self._PLClibraryHandle)
- self._PLClibraryHandle = None
-
- self.PLClibraryLock.release()
+
+ def _FreePLC(self):
+ """
+ Unload PLC library.
+ This is also called by __init__ to create dummy C func proxies
+ """
+ self.PLClibraryLock.acquire()
+ try:
+ # Unload library explicitely
+ if getattr(self, "_PLClibraryHandle", None) is not None:
+ dlclose(self._PLClibraryHandle)
+
+ # Forget all refs to library
+ self._InitPLCStubCalls()
+
+ finally:
+ self.PLClibraryLock.release()
+
return False
def PythonRuntimeCall(self, methodname):
@@ -278,6 +414,7 @@
if exp is not None:
self.LogMessage(0, '\n'.join(traceback.format_exception(*exp)))
+ # used internaly
def PythonRuntimeInit(self):
MethodNames = ["init", "start", "stop", "cleanup"]
self.python_runtime_vars = globals().copy()
@@ -329,6 +466,7 @@
self.PythonRuntimeCall("init")
+ # used internaly
def PythonRuntimeCleanup(self):
if self.python_runtime_vars is not None:
self.PythonRuntimeCall("cleanup")
@@ -340,10 +478,8 @@
res, cmd, blkid = "None", "None", ctypes.c_void_p()
compile_cache = {}
while True:
- # print "_PythonIterator(", res, ")",
cmd = self._PythonIterator(res, blkid)
FBID = blkid.value
- # print " -> ", cmd, blkid
if cmd is None:
break
try:
@@ -364,6 +500,7 @@
res = "#EXCEPTION : "+str(e)
self.LogMessage(1, ('PyEval@0x%x(Code="%s") Exception "%s"') % (FBID, cmd, str(e)))
+ @RunInMain
def StartPLC(self):
if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
c_argv = ctypes.c_char_p * len(self.argv)
@@ -382,6 +519,7 @@
self.PLCStatus = "Broken"
self.StatusChange()
+ @RunInMain
def StopPLC(self):
if self.PLCStatus == "Started":
self.LogMessage("PLC stopped")
@@ -391,40 +529,38 @@
self.StatusChange()
self.PythonRuntimeCall("stop")
if self.TraceThread is not None:
- self.TraceWakeup.set()
self.TraceThread.join()
self.TraceThread = None
return True
return False
- def _Reload(self):
- self.daemon.shutdown(True)
- self.daemon.sock.close()
- os.execv(sys.executable, [sys.executable]+sys.argv[:])
- # never reached
- return 0
-
- def ForceReload(self):
- # respawn python interpreter
- Timer(0.1, self._Reload).start()
- return True
-
+ @RunInMain
def GetPLCstatus(self):
return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount))
+ @RunInMain
def NewPLC(self, md5sum, data, extrafiles):
if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
NewFileName = md5sum + lib_ext
extra_files_log = os.path.join(self.workingdir, "extra_files.txt")
- self.UnLoadPLC()
+ old_PLC_filename = os.path.join(self.workingdir, self.CurrentPLCFilename) \
+ if self.CurrentPLCFilename is not None \
+ else None
+ new_PLC_filename = os.path.join(self.workingdir, NewFileName)
+
+ # Some platform (i.e. Xenomai) don't like reloading same .so file
+ replace_PLC_shared_object = new_PLC_filename != old_PLC_filename
+
+ if replace_PLC_shared_object:
+ self.UnLoadPLC()
self.LogMessage("NewPLC (%s)" % md5sum)
self.PLCStatus = "Empty"
try:
- os.remove(os.path.join(self.workingdir,
- self.CurrentPLCFilename))
+ if replace_PLC_shared_object:
+ os.remove(old_PLC_filename)
for filename in file(extra_files_log, "r").readlines() + [extra_files_log]:
try:
os.remove(os.path.join(self.workingdir, filename.strip()))
@@ -435,8 +571,8 @@
try:
# Create new PLC file
- open(os.path.join(self.workingdir, NewFileName),
- 'wb').write(data)
+ if replace_PLC_shared_object:
+ open(new_PLC_filename, 'wb').write(data)
# Store new PLC filename based on md5 key
open(self._GetMD5FileName(), "w").write(md5sum)
@@ -456,11 +592,12 @@
PLCprint(traceback.format_exc())
return False
- if self.LoadPLC():
+ if not replace_PLC_shared_object:
+ self.PLCStatus = "Stopped"
+ elif self.LoadPLC():
self.PLCStatus = "Stopped"
else:
self.PLCStatus = "Broken"
- self._FreePLC()
self.StatusChange()
return self.PLCStatus == "Stopped"
@@ -496,14 +633,6 @@
else:
self._suspendDebug(True)
- def _TracesPush(self, trace):
- self.TraceLock.acquire()
- lT = len(self.Traces)
- if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024:
- self.Traces.pop(0)
- self.Traces.append(trace)
- self.TraceLock.release()
-
def _TracesSwap(self):
self.LastSwapTrace = time()
if self.TraceThread is None and self.PLCStatus == "Started":
@@ -513,26 +642,9 @@
Traces = self.Traces
self.Traces = []
self.TraceLock.release()
- self.TraceWakeup.set()
return Traces
- def _TracesAutoSuspend(self):
- # TraceProc stops here if Traces not polled for 3 seconds
- traces_age = time() - self.LastSwapTrace
- if traces_age > 3:
- self.TraceLock.acquire()
- self.Traces = []
- self.TraceLock.release()
- self._suspendDebug(True) # Disable debugger
- self.TraceWakeup.clear()
- self.TraceWakeup.wait()
- self._resumeDebug() # Re-enable debugger
-
- def _TracesFlush(self):
- self.TraceLock.acquire()
- self.Traces = []
- self.TraceLock.release()
-
+ @RunInMain
def GetTraceVariables(self):
return self.PLCStatus, self._TracesSwap()
@@ -540,23 +652,47 @@
"""
Return a list of traces, corresponding to the list of required idx
"""
+ self._resumeDebug() # Re-enable debugger
while self.PLCStatus == "Started":
tick = ctypes.c_uint32()
size = ctypes.c_uint32()
buff = ctypes.c_void_p()
TraceBuffer = None
- if self.PLClibraryLock.acquire(False):
- if self._GetDebugData(ctypes.byref(tick),
- ctypes.byref(size),
- ctypes.byref(buff)) == 0:
- if size.value:
- TraceBuffer = ctypes.string_at(buff.value, size.value)
- self._FreeDebugData()
- self.PLClibraryLock.release()
+
+ self.PLClibraryLock.acquire()
+
+ res = self._GetDebugData(ctypes.byref(tick),
+ ctypes.byref(size),
+ ctypes.byref(buff))
+ if res == 0:
+ if size.value:
+ TraceBuffer = ctypes.string_at(buff.value, size.value)
+ self._FreeDebugData()
+
+ self.PLClibraryLock.release()
+
+ # leave thread if GetDebugData isn't happy.
+ if res != 0:
+ break
+
if TraceBuffer is not None:
- self._TracesPush((tick.value, TraceBuffer))
- self._TracesAutoSuspend()
- self._TracesFlush()
+ self.TraceLock.acquire()
+ lT = len(self.Traces)
+ if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024:
+ self.Traces.pop(0)
+ self.Traces.append((tick.value, TraceBuffer))
+ self.TraceLock.release()
+
+ # TraceProc stops here if Traces not polled for 3 seconds
+ traces_age = time() - self.LastSwapTrace
+ if traces_age > 3:
+ self.TraceLock.acquire()
+ self.Traces = []
+ self.TraceLock.release()
+ self._suspendDebug(True) # Disable debugger
+ break
+
+ self.TraceThread = None
def RemoteExec(self, script, *kwargs):
try:
--- a/runtime/__init__.py Tue Jun 05 15:29:58 2018 +0200
+++ b/runtime/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -24,5 +24,5 @@
from __future__ import absolute_import
import os
-from runtime.PLCObject import PLCObject, PLCprint
+from runtime.PLCObject import PLCObject, PLCprint, MainWorker
import runtime.ServicePublisher
--- a/runtime/typemapping.py Tue Jun 05 15:29:58 2018 +0200
+++ b/runtime/typemapping.py Wed Jul 04 14:17:00 2018 +0200
@@ -35,7 +35,7 @@
def _ttime():
return (IEC_TIME,
- lambda x: td(0, x.s, x.ns/1000),
+ lambda x: td(0, x.s, x.ns/1000.0),
lambda t, x: t(x.days * 24 * 3600 + x.seconds, x.microseconds*1000))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/xenomai.py Wed Jul 04 14:17:00 2018 +0200
@@ -0,0 +1,16 @@
+from ctypes import CDLL, RTLD_GLOBAL, pointer, c_int, POINTER, c_char, create_string_buffer
+def TryPreloadXenomai():
+ """
+ Xenomai 3 (at least for version <= 3.0.6) do not handle properly dlclose
+ of shared objects whose dlopen did trigger xenomai_init.
+ As a workaround, this pre-loads xenomai libraries that need to be
+ initialized and call xenomai_init once for all.
+
+ Xenomai auto init of libs MUST be disabled (see --auto-init-solib in xeno-config)
+ """
+ try:
+ for name in ["cobalt", "modechk", "copperplate", "alchemy"]:
+ globals()[name] = CDLL("lib"+name+".so", mode=RTLD_GLOBAL)
+ cobalt.xenomai_init(pointer(c_int(0)), pointer((POINTER(c_char)*2)(create_string_buffer("prog_name"), None)))
+ except:
+ pass
--- a/targets/Xenomai/__init__.py Tue Jun 05 15:29:58 2018 +0200
+++ b/targets/Xenomai/__init__.py Wed Jul 04 14:17:00 2018 +0200
@@ -37,7 +37,7 @@
if xeno_config:
from util.ProcessLogger import ProcessLogger
status, result, _err_result = ProcessLogger(self.CTRInstance.logger,
- xeno_config + " --skin=native --"+flagsname,
+ xeno_config + " --skin=posix --skin=alchemy --no-auto-init --"+flagsname,
no_stdout=True).spin()
if status:
self.CTRInstance.logger.write_error(_("Unable to get Xenomai's %s \n") % flagsname)
--- a/targets/Xenomai/plc_Xenomai_main.c Tue Jun 05 15:29:58 2018 +0200
+++ b/targets/Xenomai/plc_Xenomai_main.c Wed Jul 04 14:17:00 2018 +0200
@@ -11,10 +11,10 @@
#include <sys/mman.h>
#include <sys/fcntl.h>
-#include <native/task.h>
-#include <native/timer.h>
-#include <native/sem.h>
-#include <native/pipe.h>
+#include <alchemy/task.h>
+#include <alchemy/timer.h>
+#include <alchemy/sem.h>
+#include <alchemy/pipe.h>
unsigned int PLC_state = 0;
#define PLC_STATE_TASK_CREATED 1
@@ -37,6 +37,15 @@
#define PYTHON_PIPE_MINOR 3
#define PIPE_SIZE 1
+// rt-pipes commands
+
+#define PYTHON_PENDING_COMMAND 1
+#define PYTHON_FINISH 2
+
+#define DEBUG_FINISH 2
+
+#define DEBUG_PENDING_DATA 1
+#define DEBUG_UNLOCK 1
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
@@ -82,6 +91,18 @@
if (PLC_shutdown) break;
rt_task_wait_period(NULL);
}
+ /* since xenomai 3 it is not enough to close()
+ file descriptor to unblock read()... */
+ {
+ /* explicitely finish python thread */
+ char msg = PYTHON_FINISH;
+ rt_pipe_write(&WaitPython_pipe, &msg, sizeof(msg), P_NORMAL);
+ }
+ {
+ /* explicitely finish debug thread */
+ char msg = DEBUG_FINISH;
+ rt_pipe_write(&WaitDebug_pipe, &msg, sizeof(msg), P_NORMAL);
+ }
}
static unsigned long __debug_tick;
@@ -159,6 +180,15 @@
exit(0);
}
+#define _startPLCLog(text) \
+ {\
+ char mstr[] = text;\
+ LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));\
+ goto error;\
+ }
+
+#define FO "Failed opening "
+
#define max_val(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
{
@@ -171,49 +201,55 @@
/*** RT Pipes creation and opening ***/
/* create Debug_pipe */
- if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE) < 0)
+ _startPLCLog(FO "Debug_pipe real-time end");
PLC_state |= PLC_STATE_DEBUG_PIPE_CREATED;
/* open Debug_pipe*/
- if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1)
+ _startPLCLog(FO DEBUG_PIPE_DEVICE);
PLC_state |= PLC_STATE_DEBUG_FILE_OPENED;
/* create Python_pipe */
- if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE) < 0)
+ _startPLCLog(FO "Python_pipe real-time end");
PLC_state |= PLC_STATE_PYTHON_PIPE_CREATED;
/* open Python_pipe*/
- if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1)
+ _startPLCLog(FO PYTHON_PIPE_DEVICE);
PLC_state |= PLC_STATE_PYTHON_FILE_OPENED;
/* create WaitDebug_pipe */
- if(rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ if(rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE) < 0)
+ _startPLCLog(FO "WaitDebug_pipe real-time end");
PLC_state |= PLC_STATE_WAITDEBUG_PIPE_CREATED;
/* open WaitDebug_pipe*/
- if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1)
+ _startPLCLog(FO WAITDEBUG_PIPE_DEVICE);
PLC_state |= PLC_STATE_WAITDEBUG_FILE_OPENED;
/* create WaitPython_pipe */
- if(rt_pipe_create(&WaitPython_pipe, "WaitPython_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE))
- goto error;
+ if(rt_pipe_create(&WaitPython_pipe, "WaitPython_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE) < 0)
+ _startPLCLog(FO "WaitPython_pipe real-time end");
PLC_state |= PLC_STATE_WAITPYTHON_PIPE_CREATED;
/* open WaitPython_pipe*/
- if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
+ if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1)
+ _startPLCLog(FO WAITPYTHON_PIPE_DEVICE);
PLC_state |= PLC_STATE_WAITPYTHON_FILE_OPENED;
/*** create PLC task ***/
- if(rt_task_create(&PLC_task, "PLC_task", 0, 50, T_JOINABLE)) goto error;
+ if(rt_task_create(&PLC_task, "PLC_task", 0, 50, T_JOINABLE))
+ _startPLCLog("Failed creating PLC task");
PLC_state |= PLC_STATE_TASK_CREATED;
if(__init(argc,argv)) goto error;
/* start PLC task */
- if(rt_task_start(&PLC_task, &PLC_task_proc, NULL)) goto error;
+ if(rt_task_start(&PLC_task, &PLC_task_proc, NULL))
+ _startPLCLog("Failed starting PLC task");
return 0;
@@ -241,7 +277,6 @@
return 0;
}
-#define DEBUG_UNLOCK 1
void LeaveDebugSection(void)
{
if(AtomicCompareExchange( &debug_state,
@@ -254,7 +289,6 @@
extern unsigned long __tick;
-#define DEBUG_PENDING_DATA 1
int WaitDebugData(unsigned long *tick)
{
char cmd;
@@ -304,8 +338,6 @@
AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
}
-#define PYTHON_PENDING_COMMAND 1
-
#define PYTHON_FREE 0
#define PYTHON_BUSY 1
static long python_state = PYTHON_FREE;
--- a/targets/plc_main_head.c Tue Jun 05 15:29:58 2018 +0200
+++ b/targets/plc_main_head.c Wed Jul 04 14:17:00 2018 +0200
@@ -35,6 +35,9 @@
/* Help to quit cleanly when init fail at a certain level */
static int init_level = 0;
+/* Prototype for Logging to help spotting errors at init */
+int LogMessage(uint8_t level, char* buf, uint32_t size);
+
/*
* Prototypes of functions exported by plugins
**/
--- a/tests/tools/test_application.py Tue Jun 05 15:29:58 2018 +0200
+++ b/tests/tools/test_application.py Wed Jul 04 14:17:00 2018 +0200
@@ -41,7 +41,7 @@
class UserApplicationTest(unittest.TestCase):
def InstallExceptionHandler(self):
- def handle_exception(e_type, e_value, e_traceback):
+ def handle_exception(e_type, e_value, e_traceback, exit=False):
# traceback.print_exception(e_type, e_value, e_traceback)
self.exc_info = [e_type, e_value, e_traceback]
self.exc_info = None
@@ -89,7 +89,9 @@
# disable default exception handler in Beremiz
self.app.InstallExceptionHandler = lambda: None
self.InstallExceptionHandler()
+ self.app.handle_exception = sys.excepthook
self.app.PreStart()
+ self.ProcessEvents()
self.app.frame.Show()
self.ProcessEvents()
self.app.frame.ShowFullScreen(True)
--- a/wxglade_hmi/wxglade_hmi.py Tue Jun 05 15:29:58 2018 +0200
+++ b/wxglade_hmi/wxglade_hmi.py Wed Jul 04 14:17:00 2018 +0200
@@ -90,7 +90,10 @@
def CTNGenerate_C(self, buildpath, locations):
- hmi_frames = []
+ # list containing description of all objects declared in wxglade
+ hmi_objects = []
+ # list containing only description of the main frame object
+ main_frames = []
wxgfile_path = self._getWXGLADEpath()
if os.path.exists(wxgfile_path):
@@ -100,12 +103,17 @@
for node in wxgtree.childNodes[1].childNodes:
if node.nodeType == wxgtree.ELEMENT_NODE:
- hmi_frames.append({
- "name": node.getAttribute("name"),
+ name = node.getAttribute("name")
+ wxglade_object_desc = {
+ "name": name,
"class": node.getAttribute("class"),
"handlers": [
hnode.firstChild.data for hnode in
- node.getElementsByTagName("handler")]})
+ node.getElementsByTagName("handler")]}
+
+ hmi_objects.append(wxglade_object_desc)
+ if name == self.CTNName() :
+ main_frames.append(wxglade_object_desc)
hmipyfile_path = os.path.join(self._getBuildPath(), "hmi.py")
if wx.Platform == '__WXMSW__':
@@ -122,12 +130,12 @@
else:
define_hmi = ""
- declare_hmi = "\n".join(["%(name)s = None\n" % x +
- "\n".join(["%(class)s.%(h)s = %(h)s" %
+ declare_hmi = "\n".join(["%(name)s = None\n" % x for x in main_frames])
+ declare_hmi += "\n".join(["\n".join(["%(class)s.%(h)s = %(h)s" %
dict(x, h=h) for h in x['handlers']])
- for x in hmi_frames])
+ for x in hmi_objects])
global_hmi = ("global %s\n" % ",".join(
- [x["name"] for x in hmi_frames]) if len(hmi_frames) > 0 else "")
+ [x["name"] for x in main_frames]) if len(main_frames) > 0 else "")
init_hmi = "\n".join(["""\
def OnCloseFrame(evt):
wx.MessageBox(_("Please stop PLC to close"))
@@ -135,10 +143,10 @@
%(name)s = %(class)s(None)
%(name)s.Bind(wx.EVT_CLOSE, OnCloseFrame)
%(name)s.Show()
-""" % x for x in hmi_frames])
+""" % x for x in main_frames])
cleanup_hmi = "\n".join(
["if %(name)s is not None: %(name)s.Destroy()" % x
- for x in hmi_frames])
+ for x in main_frames])
self.PreSectionsTexts = {
"globals": define_hmi,
@@ -150,6 +158,11 @@
"start": init_hmi,
}
+ if len(main_frames) == 0 and \
+ len(getattr(self.CodeFile, "start").getanyText().strip()) == 0:
+ self.GetCTRoot().logger.write_warning(
+ _("Warning: WxGlade HMI has no object with name identical to extension name, and no python code is provided in start section to create object.\n"))
+
return PythonFileCTNMixin.CTNGenerate_C(self, buildpath, locations)
def _editWXGLADE(self):
--- a/xmlclass/xmlclass.py Tue Jun 05 15:29:58 2018 +0200
+++ b/xmlclass/xmlclass.py Wed Jul 04 14:17:00 2018 +0200
@@ -67,7 +67,7 @@
QName_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)$')
QNames_model = re.compile('((?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*(?: (?:[a-zA-Z_][\w]*:)?[a-zA-Z_][\w]*)*)$')
NCName_model = re.compile('([a-zA-Z_][\w]*)$')
-URI_model = re.compile('((?:http://|/)?(?:[\w.-]*/?)*)$')
+URI_model = re.compile('((?:htt(p|ps)://|/)?(?:[\w.-]*/?)*)$')
LANGUAGE_model = re.compile('([a-zA-Z]{1,8}(?:-[a-zA-Z0-9]{1,8})*)$')
ONLY_ANNOTATION = re.compile("((?:annotation )?)")