317 PluggedChildsWithSameClass.append(newPluginOpj) |
325 PluggedChildsWithSameClass.append(newPluginOpj) |
318 |
326 |
319 return newPluginOpj |
327 return newPluginOpj |
320 |
328 |
321 |
329 |
322 def LoadXMLParams(self, PlugName = None, test = True): |
330 def LoadXMLParams(self, PlugName = None): |
323 # Get the base xml tree |
331 # Get the base xml tree |
324 basexmlfilepath = self.PluginBaseXmlFilePath(PlugName) |
332 if self.MandatoryParams: |
325 if basexmlfilepath: |
333 basexmlfile = open(self.PluginBaseXmlFilePath(PlugName), 'r') |
326 basexmlfile = open(basexmlfilepath, 'r') |
|
327 basetree = minidom.parse(basexmlfile) |
334 basetree = minidom.parse(basexmlfile) |
328 self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0]) |
335 self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0]) |
329 basexmlfile.close() |
336 basexmlfile.close() |
330 |
337 |
331 # Get the xml tree |
338 # Get the xml tree |
332 xmlfilepath = self.PluginXmlFilePath(PlugName) |
339 if self.PlugParams: |
333 if xmlfilepath: |
340 xmlfile = open(self.PluginXmlFilePath(PlugName), 'r') |
334 xmlfile = open(xmlfilepath, 'r') |
|
335 tree = minidom.parse(xmlfile) |
341 tree = minidom.parse(xmlfile) |
336 self.PlugParams[1].loadXMLTree(tree.childNodes[0]) |
342 self.PlugParams[1].loadXMLTree(tree.childNodes[0]) |
337 xmlfile.close() |
343 xmlfile.close() |
338 |
344 |
339 if test: |
|
340 # Basic check. Better to fail immediately. |
|
341 if not PlugName: |
|
342 PlugName = os.path.split(self.PlugPath())[1].split(NameTypeSeparator)[0] |
|
343 if (self.BaseParams.getName() != PlugName): |
|
344 raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(PlugName, self.BaseParams.getName()) |
|
345 # Now, self.PlugPath() should be OK |
|
346 |
|
347 def LoadChilds(self): |
345 def LoadChilds(self): |
348 # Iterate over all PlugName@PlugType in plugin directory, and try to open them |
346 # Iterate over all PlugName@PlugType in plugin directory, and try to open them |
349 for PlugDir in os.listdir(self.PlugPath()): |
347 for PlugDir in os.listdir(self.PlugPath()): |
350 if os.path.isdir(os.path.join(self.PlugPath(), PlugDir)) and \ |
348 if os.path.isdir(os.path.join(self.PlugPath(), PlugDir)) and \ |
351 PlugDir.count(NameTypeSeparator) == 1: |
349 PlugDir.count(NameTypeSeparator) == 1: |
357 def _GetClassFunction(name): |
355 def _GetClassFunction(name): |
358 def GetRootClass(): |
356 def GetRootClass(): |
359 return getattr(__import__("plugins." + name), name).RootClass |
357 return getattr(__import__("plugins." + name), name).RootClass |
360 return GetRootClass |
358 return GetRootClass |
361 |
359 |
|
360 |
|
361 #################################################################################### |
|
362 #################################################################################### |
|
363 #################################################################################### |
|
364 ################################### ROOT ###################################### |
|
365 #################################################################################### |
|
366 #################################################################################### |
|
367 #################################################################################### |
|
368 |
|
369 iec2cc_path = os.path.join(base_folder, "matiec", "iec2cc") |
|
370 ieclib_path = os.path.join(base_folder, "matiec", "lib") |
|
371 |
|
372 # import for project creation timestamping |
|
373 from time import localtime |
|
374 from datetime import datetime |
|
375 # import necessary stuff from PLCOpenEditor |
|
376 from PLCControler import PLCControler |
|
377 from PLCOpenEditor import PLCOpenEditor, ProjectDialog |
|
378 from TextViewer import TextViewer |
|
379 from plcopen.structures import IEC_KEYWORDS |
|
380 |
362 class PluginsRoot(PlugTemplate): |
381 class PluginsRoot(PlugTemplate): |
|
382 """ |
|
383 This class define Root object of the plugin tree. |
|
384 It is responsible of : |
|
385 - Managing project directory |
|
386 - Building project |
|
387 - Handling PLCOpenEditor controler and view |
|
388 - Loading user plugins and instanciante them as childs |
|
389 - ... |
|
390 |
|
391 """ |
363 |
392 |
364 # For root object, available Childs Types are modules of the plugin packages. |
393 # For root object, available Childs Types are modules of the plugin packages. |
365 PlugChildsTypes = [(name, _GetClassFunction(name)) for name in plugins.__all__] |
394 PlugChildsTypes = [(name, _GetClassFunction(name)) for name in plugins.__all__] |
366 |
395 |
367 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
396 XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?> |
416 </xsd:complexType> |
445 </xsd:complexType> |
417 </xsd:element> |
446 </xsd:element> |
418 </xsd:schema> |
447 </xsd:schema> |
419 """ |
448 """ |
420 |
449 |
421 def __init__(self): |
450 def __init__(self, frame): |
422 PlugTemplate.__init__(self) |
451 |
|
452 self.MandatoryParams = None |
|
453 self.AppFrame = frame |
|
454 |
|
455 """ |
|
456 This method are not called here... but in NewProject and OpenProject |
|
457 self._AddParamsMembers() |
|
458 self.PluggedChilds = {} |
|
459 """ |
|
460 |
423 # self is the parent |
461 # self is the parent |
424 self.PlugParent = None |
462 self.PlugParent = None |
425 # Keep track of the plugin type name |
463 # Keep track of the plugin type name |
426 self.PlugType = "Beremiz" |
464 self.PlugType = "Beremiz" |
427 |
465 |
428 self.ProjectPath = "" |
466 # After __init__ root plugin is not valid |
|
467 self.ProjectPath = None |
429 self.PLCManager = None |
468 self.PLCManager = None |
|
469 self.PLCEditor = None |
430 |
470 |
431 def HasProjectOpened(self): |
471 def HasProjectOpened(self): |
432 """ |
472 """ |
433 Return if a project is actually opened |
473 Return if a project is actually opened |
434 """ |
474 """ |
435 return self.ProjectPath != "" |
475 return self.ProjectPath != None |
436 |
476 |
437 def GetProjectPath(self): |
477 def GetProjectPath(self): |
438 return self.ProjectPath |
478 return self.ProjectPath |
439 |
479 |
440 def NewProject(self, ProjectPath, PLCParams): |
480 def GetPlugInfos(self): |
|
481 childs = [] |
|
482 for child in self.IterChilds(): |
|
483 childs.append(child.GetPlugInfos()) |
|
484 return {"name" : os.path.split(self.ProjectPath)[1], "type" : None, "values" : childs} |
|
485 |
|
486 def NewProject(self, ProjectPath): |
441 """ |
487 """ |
442 Create a new project in an empty folder |
488 Create a new project in an empty folder |
443 @param ProjectPath: path of the folder where project have to be created |
489 @param ProjectPath: path of the folder where project have to be created |
444 @param PLCParams: properties of the PLCOpen program created |
490 @param PLCParams: properties of the PLCOpen program created |
445 """ |
491 """ |
446 # Verify that choosen folder is empty |
492 # Verify that choosen folder is empty |
447 if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0: |
493 if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0: |
448 return "Folder choosen isn't empty. You can't use it for a new project!" |
494 return "Folder choosen isn't empty. You can't use it for a new project!" |
|
495 |
|
496 dialog = ProjectDialog(self.AppFrame) |
|
497 if dialog.ShowModal() == wx.ID_OK: |
|
498 values = dialog.GetValues() |
|
499 values["creationDateTime"] = datetime(*localtime()[:6]) |
|
500 dialog.Destroy() |
|
501 else: |
|
502 dialog.Destroy() |
|
503 return "Project not created" |
|
504 |
449 # Create Controler for PLCOpen program |
505 # Create Controler for PLCOpen program |
450 self.PLCManager = PLCControler() |
506 self.PLCManager = PLCControler() |
451 self.PLCManager.CreateNewProject(PLCParams.pop("projectName")) |
507 self.PLCManager.CreateNewProject(PLCParams.pop("projectName")) |
452 self.PLCManager.SetProjectProperties(properties = PLCParams) |
508 self.PLCManager.SetProjectProperties(properties = PLCParams) |
453 # Change XSD into class members |
509 # Change XSD into class members |
454 self._AddParamsMembers() |
510 self._AddParamsMembers() |
455 self.PluggedChilds = {} |
511 self.PluggedChilds = {} |
456 # No IEC channel, name, etc... |
|
457 self.MandatoryParams = [] |
|
458 # Keep track of the root plugin (i.e. project path) |
512 # Keep track of the root plugin (i.e. project path) |
459 self.ProjectPath = ProjectPath |
513 self.ProjectPath = ProjectPath |
460 self.BaseParams.setName(os.path.split(ProjectPath)[1]) |
|
461 return None |
514 return None |
462 |
515 |
463 def LoadProject(self, ProjectPath): |
516 def LoadProject(self, ProjectPath): |
464 """ |
517 """ |
465 Load a project contained in a folder |
518 Load a project contained in a folder |
476 if result: |
529 if result: |
477 return result |
530 return result |
478 # Change XSD into class members |
531 # Change XSD into class members |
479 self._AddParamsMembers() |
532 self._AddParamsMembers() |
480 self.PluggedChilds = {} |
533 self.PluggedChilds = {} |
481 # No IEC channel, name, etc... |
|
482 self.MandatoryParams = None |
|
483 # Keep track of the root plugin (i.e. project path) |
534 # Keep track of the root plugin (i.e. project path) |
484 self.ProjectPath = ProjectPath |
535 self.ProjectPath = ProjectPath |
485 # If dir have already be made, and file exist |
536 # If dir have already be made, and file exist |
486 if os.path.isdir(self.PlugPath()) and os.path.isfile(self.PluginXmlFilePath()): |
537 if os.path.isdir(self.PlugPath()) and os.path.isfile(self.PluginXmlFilePath()): |
487 #Load the plugin.xml file into parameters members |
538 #Load the plugin.xml file into parameters members |
488 result = self.LoadXMLParams(test = False) |
539 result = self.LoadXMLParams() |
489 if result: |
540 if result: |
490 return result |
541 return result |
491 #Load and init all the childs |
542 #Load and init all the childs |
492 self.LoadChilds() |
543 self.LoadChilds() |
493 self.BaseParams.setName(os.path.split(ProjectPath)[1]) |
|
494 return None |
544 return None |
495 |
545 |
496 def SaveProject(self): |
546 def SaveProject(self): |
497 if not self.PLCManager.SaveXMLFile(): |
547 if not self.PLCManager.SaveXMLFile(): |
498 self.PLCManager.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml')) |
548 self.PLCManager.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml')) |
499 self.PlugRequestSave() |
549 self.PlugRequestSave() |
500 |
550 |
501 def PlugPath(self, PlugName=None): |
551 def PlugPath(self, PlugName=None): |
502 return self.ProjectPath |
552 return self.ProjectPath |
503 |
|
504 def PluginBaseXmlFilePath(self, PlugName=None): |
|
505 return None |
|
506 |
553 |
507 def PluginXmlFilePath(self, PlugName=None): |
554 def PluginXmlFilePath(self, PlugName=None): |
508 return os.path.join(self.PlugPath(PlugName), "beremiz.xml") |
555 return os.path.join(self.PlugPath(PlugName), "beremiz.xml") |
509 |
556 |
510 def PlugGenerate_C(self, buildpath, current_location, locations, logger): |
557 def PlugGenerate_C(self, buildpath, current_location, locations, logger): |
515 [(IEC_loc, IEC_Direction, IEC_Type, Name)]\ |
562 [(IEC_loc, IEC_Direction, IEC_Type, Name)]\ |
516 ex: [((0,0,4,5),'I','STRING','__IX_0_0_4_5'),...] |
563 ex: [((0,0,4,5),'I','STRING','__IX_0_0_4_5'),...] |
517 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
564 @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND |
518 """ |
565 """ |
519 return [(C_file_name, "") for C_file_name in self.PLCGeneratedCFiles ] , "" |
566 return [(C_file_name, "") for C_file_name in self.PLCGeneratedCFiles ] , "" |
520 |
567 |
521 def _Generate_SoftPLC(self, buildpath, logger): |
568 def _getBuildPath(self): |
522 |
569 return os.path.join(self.ProjectPath, "build") |
|
570 |
|
571 def _getIECcodepath(self): |
|
572 # define name for IEC code file |
|
573 return os.path.join(self._getBuildPath(), "plc.st") |
|
574 |
|
575 def _Generate_SoftPLC(self, logger): |
|
576 """ |
|
577 Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2CC |
|
578 @param buildpath: path where files should be created |
|
579 @param logger: the log pseudo file |
|
580 """ |
|
581 |
|
582 logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n") |
|
583 buildpath = self._getBuildPath() |
|
584 # define name for IEC code file |
|
585 plc_file = self._getIECcodepath() |
|
586 # ask PLCOpenEditor controller to write ST/IL/SFC code file |
|
587 result = self.PLCManager.GenerateProgram(plc_file) |
|
588 if not result: |
|
589 # Failed ! |
|
590 logger.write_error("Error : ST/IL/SFC code generator returned %d"%result) |
|
591 return False |
|
592 logger.write("Compiling ST Program in to C Program...\n") |
|
593 # Now compile IEC code into many C files |
|
594 # files are listed to stdout, and errors to stderr. |
|
595 status, result, err_result = logger.LogCommand("%s %s -I %s %s"%(iec2cc_path, plc_file, ieclib_path, self.TargetDir)) |
|
596 if status: |
|
597 # Failed ! |
|
598 logger.write_error("Error : IEC to C compiler returned %d"%status) |
|
599 return False |
|
600 # Now extract C files of stdout |
|
601 C_files = result.splitlines() |
|
602 # remove those that are not to be compiled because included by others |
|
603 C_files.remove("POUS.c") |
|
604 C_files.remove("LOCATED_VARIABLES.h") |
|
605 # transform those base names to full names with path |
|
606 C_files = map(lambda filename:os.path.join(self.TargetDir, filename), C_files) |
|
607 logger.write("Extracting Located Variables...\n") |
|
608 # IEC2CC compiler generate a list of located variables : LOCATED_VARIABLES.h |
|
609 location_file = open(os.path.join(buildpath,"LOCATED_VARIABLES.h")) |
|
610 locations = [] |
|
611 # each line of LOCATED_VARIABLES.h declares a located variable |
|
612 lines = [line.strip() for line in location_file.readlines()] |
|
613 # This regular expression parses the lines genereated by IEC2CC |
523 LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWD]))?,(?P<LOC>[,0-9]*)\)") |
614 LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWD]))?,(?P<LOC>[,0-9]*)\)") |
524 |
615 for line in lines: |
525 if self.PLCManager: |
616 # If line match RE, |
526 logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n") |
617 result = LOCATED_MODEL.match(line) |
527 plc_file = os.path.join(self.TargetDir, "plc.st") |
618 if result: |
528 result = self.PLCManager.GenerateProgram(plc_file) |
619 # Get the resulting dict |
529 if not result: |
620 resdict = result.groupdict() |
530 logger.write_error("Error : ST/IL/SFC code generator returned %d"%result) |
621 # rewrite string for variadic location as a tuple of integers |
531 return False |
622 resdict['LOC'] = tuple(map(int,resdict['LOC'].split(','))) |
532 logger.write("Compiling ST Program in to C Program...\n") |
623 # set located size to 'X' if not given |
533 status, result, err_result = self.LogCommand("%s %s -I %s %s"%(iec2cc_path, plc_file, ieclib_path, self.TargetDir)) |
624 if not resdict['SIZE']: |
534 if status: |
625 resdict['SIZE'] = 'X' |
535 new_dialog = wx.Frame(None) |
626 # finally store into located variable list |
536 ST_viewer = TextViewer(new_dialog, None, None) |
627 locations.append(resdict) |
537 #ST_viewer.Enable(False) |
628 # Keep track of generated C files for later use by self.PlugGenerate_C |
538 ST_viewer.SetKeywords(IEC_KEYWORDS) |
|
539 ST_viewer.SetText(file(plc_file).read()) |
|
540 new_dialog.Show() |
|
541 raise Exception, "Error : IEC to C compiler returned %d"%status |
|
542 C_files = result.splitlines() |
|
543 C_files.remove("POUS.c") |
|
544 C_files = map(lambda filename:os.path.join(self.TargetDir, filename), C_files) |
|
545 logger.write("Extracting Located Variables...\n") |
|
546 location_file = open(os.path.join(self.TargetDir,"LOCATED_VARIABLES.h")) |
|
547 locations = [] |
|
548 lines = [line.strip() for line in location_file.readlines()] |
|
549 for line in lines: |
|
550 result = LOCATED_MODEL.match(line) |
|
551 if result: |
|
552 resdict = result.groupdict() |
|
553 # rewrite location as a tuple of integers |
|
554 resdict['LOC'] = tuple(map(int,resdict['LOC'].split(','))) |
|
555 if not resdict['SIZE']: |
|
556 resdict['SIZE'] = 'X' |
|
557 locations.append(resdict) |
|
558 self.PLCGeneratedCFiles = C_files |
629 self.PLCGeneratedCFiles = C_files |
|
630 # Keep track of generated located variables for later use by self._Generate_C |
559 self.PLCGeneratedLocatedVars = locations |
631 self.PLCGeneratedLocatedVars = locations |
560 return True |
632 return True |
561 |
633 |
562 def _build(self, logger): |
634 def _build(self, logger): |
563 buildpath = os.path.join(self.ProjectPath, "build") |
635 """ |
|
636 Method called by user to (re)build SoftPLC and plugin tree |
|
637 """ |
|
638 buildpath = self._getBuildPath() |
|
639 |
|
640 # Eventually create build dir |
564 if not os.path.exists(buildpath): |
641 if not os.path.exists(buildpath): |
565 os.mkdir(buildpath) |
642 os.mkdir(buildpath) |
566 |
643 |
567 logger.write("Start build in %s" % buildpath) |
644 logger.write("Start build in %s" % buildpath) |
568 |
645 |
569 if not self._Generate_SoftPLC(buildpath, logger): |
646 # Generate SoftPLC code |
570 logger.write("SoftPLC code generation failed !") |
647 if not self._Generate_SoftPLC(logger): |
571 return |
648 logger.write_error("SoftPLC code generation failed !") |
572 |
649 return False |
|
650 |
573 logger.write("SoftPLC code generation successfull") |
651 logger.write("SoftPLC code generation successfull") |
574 |
652 |
|
653 # Generate C code and compilation params from plugin hierarchy |
575 try: |
654 try: |
576 CFilesAndCFLAGS, LDFLAGS = self._Generate_C( |
655 CFilesAndCFLAGS, LDFLAGS = self._Generate_C( |
577 buildpath, |
656 buildpath, |
578 (), |
657 (), |
579 self.PLCGeneratedLocatedVars, |
658 self.PLCGeneratedLocatedVars, |
580 logger) |
659 logger) |
581 except Exception, msg: |
660 except Exception, msg: |
582 logger.write_error("Plugins code generation Failed !") |
661 logger.write_error("Plugins code generation Failed !") |
583 logger.write_error(str(msg)) |
662 logger.write_error(str(msg)) |
584 return |
663 return False |
585 |
664 |
586 logger.write_error("Plugins code generation successfull") |
665 logger.write_error("Plugins code generation successfull") |
587 |
666 |
|
667 # Compile the resulting code into object files. |
588 for CFile, CFLAG in CFilesAndCFLAGS: |
668 for CFile, CFLAG in CFilesAndCFLAGS: |
589 print CFile,CFLAG |
669 print CFile,CFLAG |
590 |
670 |
591 LDFLAGS |
671 # Link object files into something that can be executed on target |
592 |
672 print LDFLAGS |
593 PluginMethods = [("Build",_build), ("Clean",None), ("Run",None), ("EditPLC",None), ("Simulate",None)] |
673 |
594 |
674 def _showIECcode(self, logger): |
|
675 plc_file = self._getIECcodepath() |
|
676 new_dialog = wx.Frame(None) |
|
677 ST_viewer = TextViewer(new_dialog, None, None) |
|
678 #ST_viewer.Enable(False) |
|
679 ST_viewer.SetKeywords(IEC_KEYWORDS) |
|
680 try: |
|
681 text = file(plc_file).read() |
|
682 except: |
|
683 text = '(* No IEC code have been generated at that time ! *)' |
|
684 ST_viewer.SetText(text) |
|
685 |
|
686 new_dialog.Show() |
|
687 |
|
688 def _EditPLC(self, logger): |
|
689 if not self.PLCEditor: |
|
690 self.PLCEditor = PLCOpenEditor(self, self.PLCManager) |
|
691 self.PLCEditor.RefreshProjectTree() |
|
692 self.PLCEditor.RefreshFileMenu() |
|
693 self.PLCEditor.RefreshEditMenu() |
|
694 self.PLCEditor.RefreshToolBar() |
|
695 self.PLCEditor.Show() |
|
696 |
|
697 PluginMethods = [("Build",_build), ("Clean",None), ("Run",None), ("EditPLC",None), ("Show IEC code",_showIECcode)] |
|
698 |