# HG changeset patch # User Edouard Tisserant # Date 1533728779 -7200 # Node ID 53f3eb5c47f7141dffce80febac99fcce6297c3f # Parent 62eba810212e72b42715fda655ba3b53c376f021# Parent c9915bc620cd10f8c88037b1428f1d3f8506bd73 merged Andrey's default diff -r c9915bc620cd -r 53f3eb5c47f7 Beremiz_service.py --- a/Beremiz_service.py Wed Aug 01 13:09:45 2018 +0300 +++ b/Beremiz_service.py Wed Aug 08 13:46:19 2018 +0200 @@ -30,7 +30,7 @@ import sys import getopt import threading -from threading import Thread, currentThread, Semaphore, Lock +from threading import Thread, Semaphore, Lock import traceback import __builtin__ import Pyro @@ -45,17 +45,17 @@ print(""" Usage of Beremiz PLC execution service :\n %s {[-n servicename] [-i IP] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir - -n - zeroconf service name (default:disabled) - -i - IP address of interface to bind to (default:localhost) - -p - port number default:3000 - -h - print this help text and quit - -a - autostart PLC (0:disable 1:enable) (default:0) - -x - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1) - -t - enable/disable Twisted web interface (0:disable 1:enable) (default:1) - -w - web server port or "off" to disable web server (default:8009) - -c - WAMP client default config file (default:wampconf.json) - -s - WAMP client secret, given as a file - -e - python extension (absolute path .py) + -n zeroconf service name (default:disabled) + -i IP address of interface to bind to (default:localhost) + -p port number default:3000 + -h print this help text and quit + -a autostart PLC (0:disable 1:enable) (default:0) + -x enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1) + -t enable/disable Twisted web interface (0:disable 1:enable) (default:1) + -w web server port or "off" to disable web server (default:8009) + -c WAMP client config file (can be overriden by wampconf.json in project) + -s WAMP client secret, given as a file (can be overriden by wamp.secret in project) + -e python extension (absolute path .py) working_dir - directory where are stored PLC files """ % sys.argv[0]) @@ -499,6 +499,7 @@ if havewx: wx_eval_lock = Semaphore(0) + # FIXME : beware wx mainloop is _not_ running in main thread # main_thread = currentThread() def statuschangeTskBar(status): @@ -512,6 +513,7 @@ wx_eval_lock.release() def evaluator(tocall, *args, **kwargs): + # FIXME : should implement anti-deadlock # if main_thread == currentThread(): # # avoid dead lock if called from the wx mainloop # return default_evaluator(tocall, *args, **kwargs) @@ -571,29 +573,23 @@ installThreadExcepthook() +havewamp = False if havetwisted: if webport is not None: try: import runtime.NevowServer as NS # pylint: disable=ungrouped-imports - except Exception, e: - print(_("Nevow/Athena import failed :"), e) + except Exception: + LogMessageAndException(_("Nevow/Athena import failed :")) webport = None NS.WorkingDir = WorkingDir - # Find pre-existing project WAMP config file - _wampconf = os.path.join(WorkingDir, "wampconf.json") - - # If project's WAMP config file exits, override default (-c) - if os.path.exists(_wampconf): - wampconf = _wampconf - - if wampconf is not None: - try: - import runtime.WampClient as WC # pylint: disable=ungrouped-imports - except Exception, e: - print(_("WAMP import failed :"), e) - wampconf = None + try: + import runtime.WampClient as WC # pylint: disable=ungrouped-imports + WC.WorkingDir = WorkingDir + havewamp = True + except Exception: + LogMessageAndException(_("WAMP import failed :")) # Load extensions for extention_file, extension_folder in extensions: @@ -605,22 +601,16 @@ 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. ")) - if wampconf is not None: + if havewamp: try: - _wampconf = WC.LoadWampClientConf(wampconf) - if _wampconf: - if _wampconf["url"]: # TODO : test more ? - WC.RegisterWampClient(wampconf, wampsecret) - pyruntimevars["wampsession"] = WC.GetSession - WC.SetServer(pyroserver) - else: - raise Exception(_("WAMP config is incomplete.")) - else: - raise Exception(_("WAMP config is missing.")) + WC.SetServer(pyroserver) + WC.RegisterWampClient(wampconf, wampsecret) + WC.RegisterWebSettings(NS) except Exception: LogMessageAndException(_("WAMP client startup failed. ")) diff -r c9915bc620cd -r 53f3eb5c47f7 ProjectController.py --- a/ProjectController.py Wed Aug 01 13:09:45 2018 +0300 +++ b/ProjectController.py Wed Aug 08 13:46:19 2018 +0200 @@ -65,7 +65,8 @@ base_folder = paths.AbsParentDir(__file__) -MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$") +MATIEC_ERROR_MODEL = re.compile( + ".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$") def ExtractChildrenTypesFromCatalog(catalog): @@ -94,6 +95,7 @@ class Iec2CSettings(object): + def __init__(self): self.iec2c = None self.iec2c_buildopts = None @@ -109,11 +111,12 @@ return path def findCmd(self): - cmd = "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else "") + cmd = "iec2c" + (".exe" if wx.Platform == '__WXMSW__' else "") paths = [ os.path.join(base_folder, "matiec") ] - path = self.findObject(paths, lambda p: os.path.isfile(os.path.join(p, cmd))) + path = self.findObject( + paths, lambda p: os.path.isfile(os.path.join(p, cmd))) # otherwise use iec2c from PATH if path is not None: @@ -126,7 +129,8 @@ os.path.join(base_folder, "matiec", "lib"), "/usr/lib/matiec" ] - path = self.findObject(paths, lambda p: os.path.isfile(os.path.join(p, "ieclib.txt"))) + path = self.findObject( + paths, lambda p: os.path.isfile(os.path.join(p, "ieclib.txt"))) return path def findLibCPath(self): @@ -146,7 +150,8 @@ buildopt = "" try: - # Invoke compiler. Output files are listed to stdout, errors to stderr + # Invoke compiler. + # Output files are listed to stdout, errors to stderr _status, result, _err_result = ProcessLogger(None, buildcmd, no_stdout=True, no_stderr=True).spin() @@ -186,16 +191,17 @@ - """+targets.GetTargetChoices()+""" + """ + targets.GetTargetChoices() + """ - """+((""" + """ + ((""" - """+"\n".join(['' - for libname, _lib in features.libraries])+""" + """ + "\n".join(['' + for libname, _lib, default in features.libraries]) + """ """) if len(features.libraries) > 0 else '') + """ @@ -209,6 +215,7 @@ class ProjectController(ConfigTreeNode, PLCControler): + """ This class define Root object of the confnode tree. It is responsible of : @@ -219,7 +226,8 @@ - ... """ - # For root object, available Children Types are modules of the confnode packages. + # For root object, available Children Types are modules of the confnode + # packages. CTNChildrenTypes = ExtractChildrenTypesFromCatalog(features.catalog) XSD = GetProjectControllerXSD() EditorType = ProjectNodeEditor @@ -270,8 +278,10 @@ def LoadLibraries(self): self.Libraries = [] TypeStack = [] - for libname, clsname in features.libraries: - if self.BeremizRoot.Libraries is not None and getattr(self.BeremizRoot.Libraries, "Enable_"+libname+"_Library"): + for libname, clsname, _default in features.libraries: + if self.BeremizRoot.Libraries is not None and \ + getattr(self.BeremizRoot.Libraries, + "Enable_" + libname + "_Library"): Lib = GetClassImporter(clsname)()(self, libname, TypeStack) TypeStack.append(Lib.GetTypes()) self.Libraries.append(Lib) @@ -361,7 +371,8 @@ target = self.Parser.CreateElement("TargetType", "BeremizRoot") temp_root.setTargetType(target) target_name = self.GetDefaultTargetName() - target.setcontent(self.Parser.CreateElement(target_name, "TargetType")) + target.setcontent( + self.Parser.CreateElement(target_name, "TargetType")) return target def GetParamsAttributes(self, path=None): @@ -369,7 +380,8 @@ if params[0]["name"] == "BeremizRoot": for child in params[0]["children"]: if child["name"] == "TargetType" and child["value"] == '': - child.update(self.GetTarget().getElementInfos("TargetType")) + child.update( + self.GetTarget().getElementInfos("TargetType")) return params def SetParamsAttribute(self, path, value): @@ -403,7 +415,8 @@ def _getProjectFilesPath(self, project_path=None): if project_path is not None: return os.path.join(project_path, "project_files") - projectfiles_path = os.path.join(self.GetProjectPath(), "project_files") + projectfiles_path = os.path.join( + self.GetProjectPath(), "project_files") if not os.path.exists(projectfiles_path): os.mkdir(projectfiles_path) return projectfiles_path @@ -414,7 +427,8 @@ def SetProjectDefaultConfiguration(self): # Sets default task and instance for new project - config = self.Project.getconfiguration(self.GetProjectMainConfigurationName()) + config = self.Project.getconfiguration( + self.GetProjectMainConfigurationName()) resource = config.getresource()[0].getname() config = config.getname() resource_tagname = ComputeConfigurationResourceName(config, resource) @@ -471,7 +485,8 @@ if error is not None: if self.Project is not None: (fname_err, lnum, src) = (("PLC",) + error) - self.logger.write_warning(XSDSchemaErrorMessage.format(a1=fname_err, a2=lnum, a3=src)) + self.logger.write_warning( + XSDSchemaErrorMessage.format(a1=fname_err, a2=lnum, a3=src)) else: return error, False if len(self.GetProjectConfigNames()) == 0: @@ -528,14 +543,17 @@ def CheckNewProjectPath(self, old_project_path, new_project_path): if old_project_path == new_project_path: message = (_("Save path is the same as path of a project! \n")) - dialog = wx.MessageDialog(self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR) + dialog = wx.MessageDialog( + self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR) dialog.ShowModal() return False else: plc_file = os.path.join(new_project_path, "plc.xml") if os.path.isfile(plc_file): - message = (_("Selected directory already contains another project. Overwrite? \n")) - dialog = wx.MessageDialog(self.AppFrame, message, _("Error"), wx.YES_NO | wx.ICON_ERROR) + message = ( + _("Selected directory already contains another project. Overwrite? \n")) + dialog = wx.MessageDialog( + self.AppFrame, message, _("Error"), wx.YES_NO | wx.ICON_ERROR) answer = dialog.ShowModal() return answer == wx.ID_YES return True @@ -543,7 +561,8 @@ def SaveProject(self, from_project_path=None): if self.CheckProjectPathPerm(False): if from_project_path is not None: - old_projectfiles_path = self._getProjectFilesPath(from_project_path) + old_projectfiles_path = self._getProjectFilesPath( + from_project_path) if os.path.isdir(old_projectfiles_path): shutil.copytree(old_projectfiles_path, self._getProjectFilesPath(self.ProjectPath)) @@ -558,7 +577,8 @@ path = os.getenv("USERPROFILE") else: path = os.getenv("HOME") - dirdialog = wx.DirDialog(self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON) + dirdialog = wx.DirDialog( + self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON) answer = dirdialog.ShowModal() dirdialog.Destroy() if answer == wx.ID_OK: @@ -582,7 +602,8 @@ if len(self.Libraries) == 0: return [], [], () self.GetIECProgramsAndVariables() - LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath(self.GetIECLibPath()) + LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath( + self.GetIECLibPath()) LocatedCCodeAndFlags = [] Extras = [] for lib in self.Libraries: @@ -590,7 +611,7 @@ LocatedCCodeAndFlags.append(res[:2]) if len(res) > 2: Extras.extend(res[2:]) - return map(list, zip(*LocatedCCodeAndFlags))+[tuple(Extras)] + return map(list, zip(*LocatedCCodeAndFlags)) + [tuple(Extras)] # Update PLCOpenEditor ConfNode Block types from loaded confnodes def RefreshConfNodesBlockLists(self): @@ -656,7 +677,8 @@ self.DefaultBuildPath = os.path.join(self.ProjectPath, "build") # Create a build path in temp folder else: - self.DefaultBuildPath = os.path.join(tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build") + self.DefaultBuildPath = os.path.join( + tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build") if not os.path.exists(self.DefaultBuildPath): os.makedirs(self.DefaultBuildPath) @@ -681,19 +703,23 @@ locations = [] filepath = os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h") if os.path.isfile(filepath): - # IEC2C compiler generate a list of located variables : LOCATED_VARIABLES.h - location_file = open(os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h")) + # IEC2C compiler generate a list of located variables : + # LOCATED_VARIABLES.h + location_file = open( + os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h")) # each line of LOCATED_VARIABLES.h declares a located variable lines = [line.strip() for line in location_file.readlines()] # This regular expression parses the lines genereated by IEC2C - LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P[A-Z]*),(?P[_A-Za-z0-9]*),(?P[QMI])(?:,(?P[XBWDL]))?,(?P[,0-9]*)\)") + LOCATED_MODEL = re.compile( + "__LOCATED_VAR\((?P[A-Z]*),(?P[_A-Za-z0-9]*),(?P[QMI])(?:,(?P[XBWDL]))?,(?P[,0-9]*)\)") for line in lines: # If line match RE, result = LOCATED_MODEL.match(line) if result: # Get the resulting dict resdict = result.groupdict() - # rewrite string for variadic location as a tuple of integers + # rewrite string for variadic location as a tuple of + # integers resdict['LOC'] = tuple(map(int, resdict['LOC'].split(','))) # set located size to 'X' if not given if not resdict['SIZE']: @@ -719,16 +745,20 @@ # Update PLCOpenEditor ConfNode Block types before generate ST code self.RefreshConfNodesBlockLists() - self.logger.write(_("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n")) + self.logger.write( + _("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n")) # ask PLCOpenEditor controller to write ST/IL/SFC code file - _program, errors, warnings = self.GenerateProgram(self._getIECgeneratedcodepath()) + _program, errors, warnings = self.GenerateProgram( + self._getIECgeneratedcodepath()) if len(warnings) > 0: - self.logger.write_warning(_("Warnings in ST/IL/SFC code generator :\n")) + self.logger.write_warning( + _("Warnings in ST/IL/SFC code generator :\n")) for warning in warnings: self.logger.write_warning("%s\n" % warning) if len(errors) > 0: # Failed ! - self.logger.write_error(_("Error in ST/IL/SFC code generator :\n%s\n") % errors[0]) + self.logger.write_error( + _("Error in ST/IL/SFC code generator :\n%s\n") % errors[0]) return False plc_file = open(self._getIECcodepath(), "w") # Add ST Library from confnodes @@ -763,9 +793,11 @@ self._getIECcodepath()) try: - # Invoke compiler. Output files are listed to stdout, errors to stderr + # Invoke compiler. + # Output files are listed to stdout, errors to stderr status, result, err_result = ProcessLogger(self.logger, buildcmd, - no_stdout=True, no_stderr=True).spin() + no_stdout=True, + no_stderr=True).spin() except Exception, e: self.logger.write_error(buildcmd + "\n") self.logger.write_error(repr(e) + "\n") @@ -794,29 +826,36 @@ if first_line <= i <= last_line: if last_section is not None: - self.logger.write_warning("In section: " + last_section) + self.logger.write_warning( + "In section: " + last_section) last_section = None # only write section once self.logger.write_warning("%04d: %s" % (i, line)) f.close() - self.logger.write_error(_("Error : IEC to C compiler returned %d\n") % status) + self.logger.write_error( + _("Error : IEC to C compiler returned %d\n") % status) return False # Now extract C files of stdout - C_files = [fname for fname in result.splitlines() if fname[-2:] == ".c" or fname[-2:] == ".C"] + C_files = [fname for fname in result.splitlines() if fname[ + -2:] == ".c" or fname[-2:] == ".C"] # remove those that are not to be compiled because included by others C_files.remove("POUS.c") if not C_files: - self.logger.write_error(_("Error : At least one configuration and one resource must be declared in PLC !\n")) + self.logger.write_error( + _("Error : At least one configuration and one resource must be declared in PLC !\n")) return False # transform those base names to full names with path - C_files = map(lambda filename: os.path.join(buildpath, filename), C_files) + C_files = map( + lambda filename: os.path.join(buildpath, filename), C_files) # prepend beremiz include to configuration header - H_files = [fname for fname in result.splitlines() if fname[-2:] == ".h" or fname[-2:] == ".H"] + H_files = [fname for fname in result.splitlines() if fname[ + -2:] == ".h" or fname[-2:] == ".H"] H_files.remove("LOCATED_VARIABLES.h") - H_files = map(lambda filename: os.path.join(buildpath, filename), H_files) + H_files = map( + lambda filename: os.path.join(buildpath, filename), H_files) for H_file in H_files: with file(H_file, 'r') as original: data = original.read() @@ -824,7 +863,8 @@ modified.write('#include "beremiz.h"\n' + data) self.logger.write(_("Extracting Located Variables...\n")) - # Keep track of generated located variables for later use by self._Generate_C + # Keep track of generated located variables for later use by + # self._Generate_C self.PLCGeneratedLocatedVars = self.GetLocations() # Keep track of generated C files for later use by self.CTNGenerate_C self.PLCGeneratedCFiles = C_files @@ -859,11 +899,11 @@ else: return None - ####################################################################### + # # # C CODE GENERATION METHODS # - ####################################################################### + # def CTNGenerate_C(self, buildpath, locations): """ @@ -902,7 +942,8 @@ csvfile = os.path.join(self._getBuildPath(), "VARIABLES.csv") # describes CSV columns ProgramsListAttributeName = ["num", "C_path", "type"] - VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"] + VariablesListAttributeName = [ + "num", "vartype", "IEC_path", "C_path", "type"] self._ProgramList = [] self._VariablesList = [] self._DbgVariablesList = [] @@ -922,9 +963,11 @@ # first section contains programs for line in ListGroup[0]: # Split and Maps each field to dictionnary entries - attrs = dict(zip(ProgramsListAttributeName, line.strip().split(';'))) + attrs = dict( + zip(ProgramsListAttributeName, line.strip().split(';'))) # Truncate "C_path" to remove conf an resources names - attrs["C_path"] = '__'.join(attrs["C_path"].split(".", 2)[1:]) + attrs["C_path"] = '__'.join( + attrs["C_path"].split(".", 2)[1:]) # Push this dictionnary into result. self._ProgramList.append(attrs) @@ -933,7 +976,8 @@ Idx = 0 for line in ListGroup[1]: # Split and Maps each field to dictionnary entries - attrs = dict(zip(VariablesListAttributeName, line.strip().split(';'))) + attrs = dict( + zip(VariablesListAttributeName, line.strip().split(';'))) # Truncate "C_path" to remove conf an resources names parts = attrs["C_path"].split(".", 2) if len(parts) > 2: @@ -964,7 +1008,8 @@ self._Ticktime = int(ListGroup[2][0]) except Exception: - self.logger.write_error(_("Cannot open/parse VARIABLES.csv!\n")) + self.logger.write_error( + _("Cannot open/parse VARIABLES.csv!\n")) self.logger.write_error(traceback.format_exc()) self.ResetIECProgramsAndVariables() return False @@ -1034,16 +1079,16 @@ "retrieve_calls": "\n ".join([ "__retrieve_%s();" % locstr for locstr in locstrs]), "publish_calls": "\n ".join([ # Call publish in reverse order - "__publish_%s();" % locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]), + "__publish_%s();" % locstrs[i - 1] for i in xrange(len(locstrs), 0, -1)]), "init_calls": "\n ".join([ - "init_level=%d; " % (i+1) + + "init_level=%d; " % (i + 1) + "if((res = __init_%s(argc,argv))){" % locstr + # "printf(\"%s\"); "%locstr + #for debug "return res;}" for i, locstr in enumerate(locstrs)]), "cleanup_calls": "\n ".join([ "if(init_level >= %d) " % i + - "__cleanup_%s();" % locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]) - } + "__cleanup_%s();" % locstrs[i - 1] for i in xrange(len(locstrs), 0, -1)]) + } else: plc_main_code = targets.GetCode("plc_main_head.c") % { "calls_prototypes": "\n", @@ -1052,7 +1097,8 @@ "init_calls": "\n", "cleanup_calls": "\n" } - plc_main_code += targets.GetTargetCode(self.GetTarget().getcontent().getLocalTag()) + plc_main_code += targets.GetTargetCode( + self.GetTarget().getcontent().getLocalTag()) plc_main_code += targets.GetCode("plc_main_tail.c") return plc_main_code @@ -1124,21 +1170,25 @@ buildpath, self.PLCGeneratedLocatedVars) except Exception: - self.logger.write_error(_("Runtime IO extensions C code generation failed !\n")) + self.logger.write_error( + _("Runtime IO extensions C code generation failed !\n")) self.logger.write_error(traceback.format_exc()) self.ResetBuildMD5() return False # Generate C code and compilation params from liraries try: - LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(buildpath) + LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode( + buildpath) except Exception: - self.logger.write_error(_("Runtime library extensions C code generation failed !\n")) + self.logger.write_error( + _("Runtime library extensions C code generation failed !\n")) self.logger.write_error(traceback.format_exc()) self.ResetBuildMD5() return False - self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + CTNLocationCFilesAndCFLAGS + self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \ + CTNLocationCFilesAndCFLAGS self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS ExtraFiles = CTNExtraFiles + LibExtraFiles @@ -1157,7 +1207,8 @@ del ExtraFiles # Header file for extensions - open(os.path.join(buildpath, "beremiz.h"), "w").write(targets.GetHeader()) + open(os.path.join(buildpath, "beremiz.h"), "w").write( + targets.GetHeader()) # Template based part of C code generation # files are stacked at the beginning, as files of confnode tree root @@ -1176,10 +1227,12 @@ raise Exception code_path = os.path.join(buildpath, filename) open(code_path, "w").write(code) - # Insert this file as first file to be compiled at root confnode - self.LocationCFilesAndCFLAGS[0][1].insert(0, (code_path, self.plcCFLAGS)) + # Insert this file as first file to be compiled at root + # confnode + self.LocationCFilesAndCFLAGS[0][1].insert( + 0, (code_path, self.plcCFLAGS)) except Exception: - self.logger.write_error(name+_(" generation failed !\n")) + self.logger.write_error(name + _(" generation failed !\n")) self.logger.write_error(traceback.format_exc()) self.ResetBuildMD5() return False @@ -1189,12 +1242,16 @@ def ShowError(self, logger, from_location, to_location): chunk_infos = self.GetChunkInfos(from_location, to_location) for infos, (start_row, start_col) in chunk_infos: - row = 1 if from_location[0] < start_row else (from_location[0] - start_row) - col = 1 if (start_row != from_location[0]) else (from_location[1] - start_col) + row = 1 if from_location[0] < start_row else ( + from_location[0] - start_row) + col = 1 if (start_row != from_location[0]) else ( + from_location[1] - start_col) start = (row, col) - row = 1 if to_location[0] < start_row else (to_location[0] - start_row) - col = 1 if (start_row != to_location[0]) else (to_location[1] - start_col) + row = 1 if to_location[0] < start_row else ( + to_location[0] - start_row) + col = 1 if (start_row != to_location[0]) else ( + to_location[1] - start_col) end = (row, col) if self.AppFrame is not None: @@ -1225,7 +1282,8 @@ if self._IECCodeView is None: plc_file = self._getIECcodepath() - self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name) + self._IECCodeView = IECCodeViewer( + self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name) self._IECCodeView.SetTextSyntax("ALL") self._IECCodeView.SetKeywords(IEC_KEYWORDS) try: @@ -1246,7 +1304,8 @@ if self._IECRawCodeView is None: controler = MiniTextControler(self._getIECrawcodepath(), self) - self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name) + self._IECRawCodeView = IECCodeViewer( + self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name) self._IECRawCodeView.SetTextSyntax("ALL") self._IECRawCodeView.SetKeywords(IEC_KEYWORDS) self._IECRawCodeView.RefreshView() @@ -1260,7 +1319,8 @@ elif name == "Project Files": if self._ProjectFilesView is None: - self._ProjectFilesView = FileManagementPanel(self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True) + self._ProjectFilesView = FileManagementPanel( + self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True) extensions = [] for extension, name, editor in features.file_editors: @@ -1302,7 +1362,8 @@ name = "::".join([filepath, editor_name]) editor = editors[editor_name]() - self._FileEditors[filepath] = editor(self.AppFrame.TabsOpened, self, name, self.AppFrame) + self._FileEditors[filepath] = editor( + self.AppFrame.TabsOpened, self, name, self.AppFrame) self._FileEditors[filepath].SetIcon(GetBitmap("FILE")) if isinstance(self._FileEditors[filepath], DebugViewer): self._FileEditors[filepath].SetDataProducer(self) @@ -1352,6 +1413,31 @@ if self.AppFrame is not None: self.AppFrame.LogViewer.SetLogCounters(log_count) + DefaultMethods = { + "_Run": False, + "_Stop": False, + "_Transfer": False, + "_Connect": True, + "_Disconnect": False + } + + MethodsFromStatus = { + "Started": {"_Stop": True, + "_Transfer": True, + "_Connect": False, + "_Disconnect": True}, + "Stopped": {"_Run": True, + "_Transfer": True, + "_Connect": False, + "_Disconnect": True}, + "Empty": {"_Transfer": True, + "_Connect": False, + "_Disconnect": True}, + "Broken": {"_Connect": False, + "_Disconnect": True}, + "Disconnected": {}, + } + def UpdateMethodsFromPLCStatus(self): updated = False status = None @@ -1364,32 +1450,24 @@ self._SetConnector(None, False) status = "Disconnected" if self.previous_plcstate != status: - for args in { - "Started": [("_Run", False), - ("_Stop", True)], - "Stopped": [("_Run", True), - ("_Stop", False)], - "Empty": [("_Run", False), - ("_Stop", False)], - "Broken": [], - "Disconnected": [("_Run", False), - ("_Stop", False), - ("_Transfer", False), - ("_Connect", True), - ("_Disconnect", False)], - }.get(status, []): - self.ShowMethod(*args) + allmethods = self.DefaultMethods.copy() + allmethods.update( + self.MethodsFromStatus.get(status, {})) + for method, active in allmethods.items(): + self.ShowMethod(method, active) self.previous_plcstate = status if self.AppFrame is not None: updated = True self.AppFrame.RefreshStatusToolBar() if status == "Disconnected": - self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 1) + self.AppFrame.ConnectionStatusBar.SetStatusText( + self.GetTextStatus(status), 1) self.AppFrame.ConnectionStatusBar.SetStatusText('', 2) else: self.AppFrame.ConnectionStatusBar.SetStatusText( _("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), 1) - self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 2) + self.AppFrame.ConnectionStatusBar.SetStatusText( + self.GetTextStatus(status), 2) return updated def GetTextStatus(self, status): @@ -1399,12 +1477,13 @@ "Empty": _("Empty"), "Broken": _("Broken"), "Disconnected": _("Disconnected") - } + } return msgs.get(status, status) def ShowPLCProgress(self, status="", progress=0): self.AppFrame.ProgressStatusBar.Show() - self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 1) + self.AppFrame.ConnectionStatusBar.SetStatusText( + self.GetTextStatus(status), 1) self.AppFrame.ProgressStatusBar.SetValue(progress) def HidePLCProgress(self): @@ -1420,19 +1499,23 @@ 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()] + # 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) + 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) + 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] + forced = IECdebug_data[2:4] == [ + "Forced", value] if not IECdebug_data[4] and len(values_buffer) > 0: values_buffer[-1] = (value, forced) else: @@ -1461,14 +1544,17 @@ IECPathsToPop.append(IECPath) elif IECPath != "__tick__": # Convert - Idx, IEC_Type = self._IECPathToIdx.get(IECPath, (None, None)) + Idx, IEC_Type = self._IECPathToIdx.get( + IECPath, (None, None)) if Idx is not None: if IEC_Type in DebugTypesSize: Idxs.append((Idx, IEC_Type, fvalue, IECPath)) else: - self.logger.write_warning(_("Debug: Unsupported type to debug '%s'\n") % IEC_Type) + self.logger.write_warning( + _("Debug: Unsupported type to debug '%s'\n") % IEC_Type) else: - self.logger.write_warning(_("Debug: Unknown variable '%s'\n") % IECPath) + self.logger.write_warning( + _("Debug: Unknown variable '%s'\n") % IECPath) for IECPathToPop in IECPathsToPop: self.IECdebug_datas.pop(IECPathToPop) @@ -1495,8 +1581,10 @@ # Links between PLC located variables and real variables are not ready if self.IsPLCStarted(): # Timer to prevent rapid-fire when registering many variables - # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead - self.DebugTimer = Timer(0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector]) + # use wx.CallAfter use keep using same thread. TODO : use wx.Timer + # instead + self.DebugTimer = Timer( + 0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector]) # Rearm anti-rapid-fire timer self.DebugTimer.start() @@ -1600,9 +1688,11 @@ if len(self.TracedIECPath) == len(buffers): for IECPath, values in izip(self.TracedIECPath, buffers): if len(values) > 0: - self.CallWeakcallables(IECPath, "NewValues", debug_ticks, values) + self.CallWeakcallables( + IECPath, "NewValues", debug_ticks, values) if len(debug_ticks) > 0: - self.CallWeakcallables("__tick__", "NewDataAvailable", debug_ticks) + self.CallWeakcallables( + "__tick__", "NewDataAvailable", debug_ticks) delay = time.time() - start_time next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay) @@ -1667,7 +1757,8 @@ def _Connect(self): # don't accept re-connetion if already connected if self._connector is not None: - self.logger.write_error(_("Already connected. Please disconnect\n")) + self.logger.write_error( + _("Already connected. Please disconnect\n")) return # Get connector uri @@ -1705,7 +1796,8 @@ try: self._SetConnector(connectors.ConnectorFactory(uri, self)) except Exception: - self.logger.write_error(_("Exception while connecting %s!\n") % uri) + self.logger.write_error( + _("Exception while connecting %s!\n") % uri) self.logger.write_error(traceback.format_exc()) # Did connection success ? @@ -1713,10 +1805,6 @@ # Oups. self.logger.write_error(_("Connection failed to %s!\n") % uri) else: - self.ShowMethod("_Connect", False) - self.ShowMethod("_Disconnect", True) - self.ShowMethod("_Transfer", True) - self.CompareLocalAndRemotePLC() # Init with actual PLC status and print it @@ -1726,7 +1814,8 @@ self.logger.write(_("Debugger ready\n")) self._connect_debug() else: - self.logger.write_warning(_("Debug does not match PLC - stop/transfert/start to re-enable\n")) + self.logger.write_warning( + _("Debug does not match PLC - stop/transfert/start to re-enable\n")) def CompareLocalAndRemotePLC(self): if self._connector is None: @@ -1737,7 +1826,8 @@ if MD5 is not None: if not self._connector.MatchMD5(MD5): # self.logger.write_warning( - # _("Latest build does not match with target, please transfer.\n")) + # _("Latest build does not match with target, please + # transfer.\n")) self.EnableMethod("_Transfer", True) else: # self.logger.write( @@ -1770,7 +1860,8 @@ # Check if md5 file is empty : ask user to build PLC if MD5 is None: - self.logger.write_error(_("Failed : Must build before transfer.\n")) + self.logger.write_error( + _("Failed : Must build before transfer.\n")) return False # Compare PLC project with PLC on target @@ -1805,7 +1896,8 @@ self.logger.write_error(_("Transfer failed\n")) self.HidePLCProgress() else: - self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n")) + self.logger.write_error( + _("No PLC to transfer (did build succeed ?)\n")) wx.CallAfter(self.UpdateMethodsFromPLCStatus) diff -r c9915bc620cd -r 53f3eb5c47f7 bacnet/BacnetSlaveEditor.py --- a/bacnet/BacnetSlaveEditor.py Wed Aug 01 13:09:45 2018 +0300 +++ b/bacnet/BacnetSlaveEditor.py Wed Aug 08 13:46:19 2018 +0200 @@ -23,329 +23,327 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from __future__ import absolute_import +from collections import Counter + import wx -from collections import Counter -from pickle import dump + +# Import some libraries on Beremiz code from util.BitmapLibrary import GetBitmap - - - -# Import some libraries on Beremiz code... -from controls.CustomGrid import CustomGrid -from controls.CustomTable import CustomTable +from controls.CustomGrid import CustomGrid +from controls.CustomTable import CustomTable from editors.ConfTreeNodeEditor import ConfTreeNodeEditor -from graphics.GraphicCommons import ERROR_HIGHLIGHT - +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 - - - +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 +# 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: +class ObjectProperties(object): # this __init_() function is currently not beeing used! + def __init__(self): - #nothing to do + # 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} - } - + # '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, + # syntax for GridCellNumberEditor -> "min,max" + # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 + "GridCellEditorParam": "0,4194302"}, + "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" + # '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 @@ -353,200 +351,194 @@ # 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", ... + 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"}, + "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, + # use string renderer with choice editor! + "GridCellRenderer": wx.grid.GridCellStringRenderer, + # syntax for GridCellChoiceEditor -> comma separated values + "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits])} + } + + # 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] - - + 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 - } - } - + # '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"}, + "Object Name": {"GridCellEditor": wx.grid.GridCellTextEditor, + "GridCellRenderer": wx.grid.GridCellStringRenderer}, + "Description": {"GridCellEditor": wx.grid.GridCellTextEditor, + "GridCellRenderer": wx.grid.GridCellStringRenderer}, + # MultiState Values are encoded in unsigned integer + # (in BACnet => uint8_t), and can not be 0. + # See ASHRAE 135-2016, section 12.20.4 + "Number of States": {"GridCellEditor": wx.grid.GridCellNumberEditor, + "GridCellRenderer": wx.grid.GridCellNumberRenderer, + # syntax for GridCellNumberEditor -> "min,max" + "GridCellEditorParam": "1,255"} + } # 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 +# 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" - } + 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" - } + 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" - } + 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" - } + 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" - } + 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" - } + 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" - } + 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" - } + 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" - } - - - - - - - - + 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 + # + # 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 + # 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, + # 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"} - # + # 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. + # 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 @@ -560,35 +552,37 @@ # (in particular, the UpdateVirtualProperties() method) # # The base class must be initialized *first* - CustomTable.__init__(self, parent, data, BACnetObjectType.PropertyNames) + 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 + 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!!!" + # print "ObjectTable._updateColAttrs() called!!!" for row in range(self.GetNumberRows()): for col in range(self.GetNumberCols()): - PropertyName = self.BACnetObjectType.PropertyNames[col] + 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.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) + grid.SetCellTextColour(row, col, wx.BLACK) if "GridCellEditorParam" in PropertyConfig: - grid.GetCellEditor(row, col).SetParameters(PropertyConfig["GridCellEditorParam"]) + grid.GetCellEditor(row, col).SetParameters( + PropertyConfig["GridCellEditorParam"]) self.ResizeRow(grid, row) def FindValueByName(self, PropertyName, PropertyValue): @@ -600,8 +594,8 @@ if int(self.GetValueByName(row, PropertyName)) == PropertyValue: return row return None - - # Return a list containing all the values under the column named 'colname' + + # Return a list containing all the values under the column named 'colname' def GetAllValuesByName(self, colname): values = [] for row in range(self.GetNumberRows()): @@ -610,13 +604,15 @@ # Returns a dictionary with: # keys: IDs of BACnet objects - # value: number of BACnet objects using this same Id + # 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 + # 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... + # list of integers instead of strings... + ObjectIDs_as_int = [int(x) for x in ObjectIDs] # 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)) @@ -639,11 +635,10 @@ 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 + # + # Needed mostly to customise the initial values of newly added rows, and to # validate if the inserted data follows BACnet rules. # # @@ -656,82 +651,87 @@ # 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, + # 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() + # 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 + # start off with invalid object ID + self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL) # 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 = int( + self.Table.GetValueByName(new_row - 1, "Object Identifier")) new_object_id += 1 - # Check whether the chosen object ID is not already in use. + # 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 + # 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. + # 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) + 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! + # 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... + # 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.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.SetCellTextColour(row, 0, wx.BLACK) + # Refresh the graphical display to take into account any changes we may + # have made self.ForceRefresh() - return None - + return None # Called when the user changes the name of BACnet object (using the GUI grid) - # call graph: ObjectEditor.OnVariablesGridCellChange() --> + # 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 + # 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. @@ -743,18 +743,17 @@ # 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... + # This is an error! Highlight it... self.SetCellBackgroundColour(row, 1, ERROR_HIGHLIGHT[0]) - self.SetCellTextColour (row, 1, ERROR_HIGHLIGHT[1]) - else: + 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.SetCellTextColour(row, 1, wx.BLACK) + # Refresh the graphical display to take into account any changes we may + # have made self.ForceRefresh() - return None - - - + return None + class ObjectEditor(wx.Panel): # This ObjectEditor class: @@ -763,14 +762,15 @@ # 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 + # 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 + # controller: The ConfigTreeNode object that controlls the data presented by # this 'config tree node editor' # # parent: wx._controls.Notebook @@ -778,57 +778,62 @@ # 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, + # (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.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: + # 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) + 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.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) + ("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 + 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) + # use only to enable drag'n'drop + # self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) + 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' + # do not include a leftmost column containing the 'row label' + self.VariablesGrid.SetRowLabelSize(0) self.VariablesGrid.SetButtons({"Add": self.AddButton, "Delete": self.DeleteButton, "Up": self.UpButton, @@ -837,10 +842,11 @@ # 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.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 + # 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) @@ -848,19 +854,19 @@ # (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()): + 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()) - + 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 @@ -872,62 +878,57 @@ # (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. + # calls ObjectTable._updateColAttrs(), who will do the configuring. self.ObjTable.ResetView(self.VariablesGrid) - def RefreshView(self): - #print "ObjectEditor.RefreshView() called!!!" + # 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 - - ######################################### + # self.window.HighlightAllDuplicateObjectNames() + + # # 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) + col = 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. + # 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. + # 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 + # 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() + pass def OnVariablesGridEditorShown(self, event): - row, col = event.GetRow(), event.GetCol() + pass 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"), @@ -938,53 +939,62 @@ (_("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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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 @@ -1013,17 +1023,21 @@ 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 + # 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() + + # Refresh the title of the Beremiz window + # (it changes depending on whether there are + # changes to save!! ) + self.ParentWindow.RefreshTitle() + + # 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.RefreshFileMenu() + + # self.ParentWindow.RefreshEditMenu() + # self.ParentWindow.RefreshPageTitles() diff -r c9915bc620cd -r 53f3eb5c47f7 bacnet/__init__.py --- a/bacnet/__init__.py Wed Aug 01 13:09:45 2018 +0300 +++ b/bacnet/__init__.py Wed Aug 08 13:46:19 2018 +0200 @@ -18,9 +18,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# +# # This code is made available on the understanding that it will not be # used in safety-critical situations without a full and competent review. -from bacnet import * +from __future__ import absolute_import + +from bacnet.bacnet import * diff -r c9915bc620cd -r 53f3eb5c47f7 bacnet/bacnet.py --- a/bacnet/bacnet.py Wed Aug 01 13:09:45 2018 +0300 +++ b/bacnet/bacnet.py Wed Aug 08 13:46:19 2018 +0200 @@ -19,51 +19,53 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# +# # This code is made available on the understanding that it will not be # used in safety-critical situations without a full and competent review. - - -import os, sys +from __future__ import absolute_import + +import os 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") +from datetime import datetime +import pickle + +import wx + +from bacnet.BacnetSlaveEditor import * +from bacnet.BacnetSlaveEditor import ObjectProperties +from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY + +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_ID = 9999 BACNET_VENDOR_NAME = "Beremiz.org" BACNET_DEVICE_MODEL_NAME = "Beremiz PLC" -################################################### -################################################### -# # -# S L A V E D E V I C E # -# # -################################################### -################################################### +# +# +# +# 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: +# class _BacnetSlavePlug: + + +class RootClass(object): XSD = """ @@ -77,7 +79,7 @@ - @@ -99,8 +101,7 @@ # 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. @@ -117,17 +118,17 @@ # 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 + # 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 _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 + # the user closes the corresponding tab in the # editing panel, and a new object is created when # the editor is re-opened. # @@ -136,9 +137,9 @@ # and are therefore stored to a file) # # _BacnetSlavePlug contains: AV_VarEditor, ... - # (these are the objects that implement a grid table to edit/view the + # (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 @@ -149,102 +150,117 @@ # 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"}, + {"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 + # 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. - + + # Each list will contain an entry for each row in the xxxxVar grid!! + # Each entry/row will be a dictionary + # Each dictionary will contain all entries/data + # for one row in the grid. + + self.ObjTablesData["AV_Obj"] = [] + self.ObjTablesData["AO_Obj"] = [] + self.ObjTablesData["AI_Obj"] = [] + self.ObjTablesData["BV_Obj"] = [] + self.ObjTablesData["BO_Obj"] = [] + self.ObjTablesData["BI_Obj"] = [] + self.ObjTablesData["MSV_Obj"] = [] + self.ObjTablesData["MSO_Obj"] = [] + self.ObjTablesData["MSI_Obj"] = [] + + 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)): + 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 <--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - - ###################################### + 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) + + # # 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. - + # 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()) - + 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. + # (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 + # 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 - + # 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)) - + locstr = ".".join(map(str, current_location)) + # IDs used by BACnet to identify object types/class. # OBJECT_ANALOG_INPUT = 0, # OBJECT_ANALOG_OUTPUT = 1, @@ -270,78 +286,84 @@ # 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} - - - ############################ + 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: + 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?? + "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? + "description": "description", # seems to be ignored? "children": []}) - - BACnetEntries = [] - return {"name": name, - "type": LOCATION_CONFNODE, - "location": location_str + ".x", - "children": BACnetObjectEntries} - - + + 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 + # 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_ + # 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")) + 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): @@ -354,26 +376,25 @@ # 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 = 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 + # 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 @@ -383,19 +404,20 @@ 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 + # 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 + return _("Unable to save to file \"%s\"!") % filepath def LoadFromFile(self, filepath): # Load the data that is saved in SaveToFile() @@ -405,157 +427,181 @@ fd.close() return True except: - return _("Unable to load file \"%s\"!")%filepath + 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(), + 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) + 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() - + 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() + 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 + + # this should be an object of class ProjectController + CTN_Root = self.GetCTRoot() + + # 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 + Project = CTN_Root.Project 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 = "" + # 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"] + ProjName = ProjProp["projectName"] ProjAuthor = "" if "companyName" in FileProp: ProjAuthor += "(" + FileProp["companyName"] + ")" if "authorName" in ProjProp: - ProjAuthor = ProjProp["authorName"] + " " + ProjAuthor - + 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"] + 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;;;;;" - + 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 ) - ]: + 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_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 = 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! + + # 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_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 = 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 + # + 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 + # 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... + 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) + 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) - - #------------------------------------------------------------------------------- + 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["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_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 @@ -563,90 +609,95 @@ # 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 + # 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 + # 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 + # 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}' + '%(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"}' + '%(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 ) - ]: + '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}' + + # see the comment in GetVariableLocationTree() + AV_locstr = 'MD' + locstr + '_2' + AO_locstr = 'QD' + locstr + '_1' + AI_locstr = 'ID' + locstr + '_0' + BV_locstr = 'MX' + locstr + '_5' + BO_locstr = 'QX' + locstr + '_4' + BI_locstr = 'IX' + locstr + '_3' + MSV_locstr = 'MB' + locstr + '_19' + MSO_locstr = 'QB' + locstr + '_14' + MSI_locstr = 'IB' + locstr + '_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 + 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 ) - - #---------------------------------------------------------------------- + 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"] + 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" - ] + 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)) + 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() + 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") @@ -654,44 +705,23 @@ 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")) - - #---------------------------------------------------------------------- + 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 + '"') + # 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+'"' - + LDFLAGS.append( + ' "' + os.path.join(BacnetLibraryPath, "libbacnet.a") + '"') + + CFLAGS = ' -I"' + BacnetIncludePath + '"' + CFLAGS += ' -I"' + BacnetIncludePortPath + '"' + return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True - - - -################################################### -################################################### -# # -# R O O T C L A S S # -# # -################################################### -################################################### -#class RootClass: - #XSD = """ - # - # - # - # - #""" - #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug, "Bacnet Slave") - ##,("XXX",_XXXXPlug, "XXX") - #] - - #def CTNGenerate_C(self, buildpath, locations): - #return [], "", True diff -r c9915bc620cd -r 53f3eb5c47f7 editors/CodeFileEditor.py --- a/editors/CodeFileEditor.py Wed Aug 01 13:09:45 2018 +0300 +++ b/editors/CodeFileEditor.py Wed Aug 08 13:46:19 2018 +0200 @@ -598,7 +598,31 @@ # Helper for VariablesGrid values # ------------------------------------------------------------------------------- +class AllGridCellEditor(wx.grid.GridCellTextEditor): + def __init__(self, table, row, col): + wx.grid.GridCellTextEditor.__init__(self) + + +class ClassGridCellEditor(wx.grid.GridCellChoiceEditor): + def __init__(self, table, row, col): + wx.grid.GridCellChoiceEditor.__init__(self) + self.SetParameters("input,memory,output") + + class VariablesTable(CustomTable): + __defaultColumnType = dict( + [(name, AllGridCellEditor) for name in + ["Name", "Initial", "Description", "OnChange", "Options"]] + + [('Class', ClassGridCellEditor), ('Type', None)]) + + def __init__(self, *args, **kwargs): + my_columns = kwargs.pop("additional_columns") + super(VariablesTable, self).__init__(*args, **kwargs) + self.columnTypes = dict(self.__defaultColumnType) + if my_columns is not None: + for key in my_columns.keys(): + if key in self.columnTypes.keys(): + self.columnTypes[key] = my_columns[key] def GetValue(self, row, col): if row < self.GetNumberRows(): @@ -621,15 +645,9 @@ renderer = None colname = self.GetColLabelValue(col, False) - if colname in ["Name", "Initial", "Description", "OnChange", "Options"]: - editor = wx.grid.GridCellTextEditor() - elif colname == "Class": - editor = wx.grid.GridCellChoiceEditor() - editor.SetParameters("input,memory,output") - elif colname == "Type": - pass - else: - grid.SetReadOnly(row, col, True) + editortype = self.columnTypes.get(colname, None) + if editortype is not None: + editor = editortype(self, row, col) grid.SetCellEditor(row, col, editor) grid.SetCellRenderer(row, col, renderer) @@ -640,7 +658,7 @@ class VariablesEditor(wx.Panel): - def __init__(self, parent, window, controler): + def __init__(self, parent, window, controler, additional_columns=None): wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=4) @@ -680,7 +698,8 @@ "OnChange": "", "Options": "" } - self.Table = VariablesTable(self, [], self.GetVariableTableColnames()) + + self.Table = VariablesTable(self, [], self.GetVariableTableColnames(), additional_columns=additional_columns) self.ColAlignements = [wx.ALIGN_RIGHT] + \ [wx.ALIGN_LEFT]*(len(self.VariablesDefaultValue)) self.ColSizes = [20, 150] + [130]*(len(self.VariablesDefaultValue)-1) @@ -845,6 +864,7 @@ CONFNODEEDITOR_TABS = [] CODE_EDITOR = None + COLUMNS_TYPE = None def _create_CodePanel(self, prnt): self.CodeEditorPanel = wx.SplitterWindow(prnt) @@ -852,7 +872,8 @@ self.VariablesPanel = VariablesEditor(self.CodeEditorPanel, self.ParentWindow, - self.Controler) + self.Controler, + self.COLUMNS_TYPE) if self.CODE_EDITOR is not None: self.CodeEditor = self.CODE_EDITOR(self.CodeEditorPanel, diff -r c9915bc620cd -r 53f3eb5c47f7 features.py --- a/features.py Wed Aug 01 13:09:45 2018 +0300 +++ b/features.py Wed Aug 08 13:46:19 2018 +0200 @@ -9,10 +9,10 @@ # See COPYING file for copyrights details. libraries = [ - ('Native', 'NativeLib.NativeLibrary'), - ('Python', 'py_ext.PythonLibrary'), - ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary'), - ('SVGUI', 'svgui.SVGUILibrary')] + ('Native', 'NativeLib.NativeLibrary', True), + ('Python', 'py_ext.PythonLibrary', True), + ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False), + ('SVGUI', 'svgui.SVGUILibrary', False)] catalog = [ ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'), diff -r c9915bc620cd -r 53f3eb5c47f7 runtime/NevowServer.py --- a/runtime/NevowServer.py Wed Aug 01 13:09:45 2018 +0300 +++ b/runtime/NevowServer.py Wed Aug 08 13:46:19 2018 +0200 @@ -26,10 +26,19 @@ from __future__ import absolute_import from __future__ import print_function import os -from nevow import appserver, inevow, tags, loaders, athena +import platform as platform_module +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 = ''' - + diff -r c9915bc620cd -r 53f3eb5c47f7 tests/wamp/project_files/wampconf.json --- a/tests/wamp/project_files/wampconf.json Wed Aug 01 13:09:45 2018 +0300 +++ b/tests/wamp/project_files/wampconf.json Wed Aug 08 13:46:19 2018 +0200 @@ -1,7 +1,10 @@ { - "url":"ws://127.0.0.1:8888", - "realm":"Automation", - "ID":"wamptest", - "password":"1234567890", - "key":"ABCDEFGHIJ" + "ID": "wamptest", + "active": true, + "protocolOptions": { + "autoPingInterval": 60, + "autoPingTimeout": 20 + }, + "realm": "Automation", + "url": "ws://127.0.0.1:8888" } diff -r c9915bc620cd -r 53f3eb5c47f7 wxglade_hmi/wxglade_hmi.py --- a/wxglade_hmi/wxglade_hmi.py Wed Aug 01 13:09:45 2018 +0300 +++ b/wxglade_hmi/wxglade_hmi.py Wed Aug 08 13:46:19 2018 +0200 @@ -80,7 +80,8 @@ if wx.Platform == '__WXMSW__': glade = "\"%s\"" % glade mode = {False: os.P_NOWAIT, True: os.P_WAIT}[wait] - os.spawnv(mode, sys.executable, ["\"%s\"" % sys.executable] + [glade] + options) + os.spawnv(mode, sys.executable, + ["\"%s\"" % sys.executable] + [glade] + options) def OnCTNSave(self, from_project_path=None): if from_project_path is not None: @@ -92,7 +93,7 @@ # list containing description of all objects declared in wxglade hmi_objects = [] - # list containing only description of the main frame object + # list containing only description of the main frame object main_frames = [] wxgfile_path = self._getWXGLADEpath() @@ -112,7 +113,7 @@ node.getElementsByTagName("handler")]} hmi_objects.append(wxglade_object_desc) - if name == self.CTNName() : + if name == self.CTNName(): main_frames.append(wxglade_object_desc) hmipyfile_path = os.path.join(self._getBuildPath(), "hmi.py") @@ -121,7 +122,8 @@ wxghmipyfile_path = "\"%s\"" % hmipyfile_path else: wxghmipyfile_path = hmipyfile_path - self.launch_wxglade(['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True) + self.launch_wxglade( + ['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True) hmipyfile = open(hmipyfile_path, 'r') define_hmi = hmipyfile.read().decode('utf-8') @@ -132,8 +134,8 @@ 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_objects]) + dict(x, h=h) for h in x['handlers']]) + for x in hmi_objects]) global_hmi = ("global %s\n" % ",".join( [x["name"] for x in main_frames]) if len(main_frames) > 0 else "") init_hmi = "\n".join(["""\ @@ -160,9 +162,9 @@ 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")) - + 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):