├── tia_xml_generator ├── __init__.py ├── elements │ ├── __init__.py │ ├── blocks │ │ ├── __init__.py │ │ ├── db.py │ │ ├── ob.py │ │ └── fb.py │ ├── attribute_list.py │ ├── object_list.py │ ├── multi_language_text.py │ ├── comment.py │ ├── wires.py │ ├── section.py │ ├── parts │ │ ├── part.py │ │ ├── call.py │ │ └── __init__.py │ ├── wire.py │ ├── multilingual_text.py │ ├── network_source.py │ ├── interface.py │ ├── member.py │ ├── basis.py │ ├── compile_unit.py │ ├── flg_net.py │ └── document.py └── enums.py ├── .gitattributes ├── requirements.txt ├── LICENSE ├── README.md └── .gitignore /tia_xml_generator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Repsay/tia-portal-xml-generator/HEAD/requirements.txt -------------------------------------------------------------------------------- /tia_xml_generator/elements/blocks/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from typing import Protocol 4 | 5 | 6 | class Block(Protocol): 7 | ... 8 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/attribute_list.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.basis import XMLBase 2 | import xml.etree.ElementTree as ET 3 | 4 | class AttributeList(XMLBase): 5 | element_name = "AttributeList" 6 | 7 | def __init__(self): 8 | super().__init__() 9 | self.element = ET.Element(self.element_name) 10 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/object_list.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | from tia_xml_generator.elements.basis import XMLBase, XMLElement 3 | import xml.etree.ElementTree as ET 4 | 5 | T = TypeVar("T", bound=XMLElement) 6 | 7 | class ObjectList(XMLBase): 8 | element_name = "ObjectList" 9 | 10 | def __init__(self): 11 | super().__init__() 12 | self.element = ET.Element(self.element_name) -------------------------------------------------------------------------------- /tia_xml_generator/elements/multi_language_text.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.basis import XMLBase 2 | import xml.etree.ElementTree as ET 3 | 4 | class MultiLanguageText(XMLBase): 5 | element_name = "MultiLanguageText" 6 | 7 | def __init__(self, text: str, language: str = "en-US"): 8 | super().__init__() 9 | self.element = ET.Element(self.element_name, {"Lang": language}) 10 | self.element.text = text 11 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/comment.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.basis import XMLBase 2 | import xml.etree.ElementTree as ET 3 | 4 | from tia_xml_generator.elements.multi_language_text import MultiLanguageText 5 | 6 | class Comment(XMLBase): 7 | element_name = "Comment" 8 | 9 | def __init__(self, comment: str, language: str = "en-US"): 10 | super().__init__() 11 | self.element = ET.Element(self.element_name) 12 | 13 | self.add(comment, language) 14 | 15 | def add(self, comment: str, language: str = "en-US") -> None: 16 | self.children.append(MultiLanguageText(comment, language)) 17 | -------------------------------------------------------------------------------- /tia_xml_generator/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ProgrammingLanguage(Enum): 5 | LAD = 1 6 | FBD = 2 7 | DB = 3 8 | GRAPH = 4 9 | 10 | 11 | class Remanence(Enum): 12 | SetInIDB = 1 13 | NonRetain = 2 14 | Retain = 3 15 | 16 | 17 | class Accessibility(Enum): 18 | Public = 1 19 | Internal = 2 20 | Protected = 3 21 | Private = 4 22 | 23 | 24 | class MemoryLayout(Enum): 25 | Standard = 1 26 | Optimized = 2 27 | 28 | 29 | class SecondaryType(Enum): 30 | ProgramCycle = 1 31 | ProgrammingError = 121 32 | RackOrStationFailure = 86 33 | Startup = 100 34 | DiagnosticErrorInterrupt = 82 35 | PullOrPlugOfModules = 83 36 | TimeDelayInterrupt = 20 37 | CyclicInterrupt = 30 38 | HardwareInterrupt = 40 39 | TimeErrorInterrupt = 80 40 | IOAccessError = 122 41 | TimeOfDay = 10 42 | SynchronousCycle = 61 43 | Status = 55 44 | Update = 56 45 | Profile = 57 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jasper Delahaije 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tia-xml-generator 2 | 3 | tia-xml-generator is a Python library created for generating XML files that can be imported into TIA Portal via TIA Openness. The library was created as part of a graduation project and currently supports the creation of DB, FB, and OB blocks. 4 | 5 | ## Usage 6 | 7 | Here's an example of how to use tia-xml-generator to create a DB block: 8 | 9 | ```python 10 | from tia_xml_generator.elements import document 11 | from tia_xml_generator.enums import MemoryLayout 12 | 13 | doc = document.Document() 14 | db = doc.add_db("__NAME__") 15 | db.add_static("__NAME__", "__TYPE__") 16 | db.author = "__Author__" 17 | db.family = "__Family__" 18 | db.version = "1.0" 19 | db.memory_layout = MemoryLayout.Standard.name 20 | doc.save_template("__TEMPLATE_NAME__", True) # Gives PKL file 21 | doc.save("__OUTPUT_NAME__") # Gives XML file 22 | ``` 23 | 24 | The code above creates a new instance of the Document class from tia_xml_generator.elements, adds a DB block to it, sets some properties of the block, saves the block as a pkl file using the save_template method, and saves the entire document as an XML file using the save method. 25 | 26 | ## Contributing 27 | 28 | If you'd like to contribute to tia-xml-generator, please fork the repository and create a new branch for your changes. Once you've made your changes, submit a pull request and we'll review your changes. 29 | 30 | ## License 31 | 32 | tia-xml-generator is licensed under the MIT license. See the LICENSE file for more information. 33 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/wires.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | from tia_xml_generator.elements.parts.call import Call 5 | from tia_xml_generator.elements.parts.part import Part 6 | 7 | from tia_xml_generator.elements.wire import Wire 8 | 9 | class Wires(XMLBase): 10 | element_name = "Wires" 11 | 12 | def __init__(self): 13 | super().__init__() 14 | self.element = ET.Element(self.element_name) 15 | self.wire_id = 21 16 | 17 | def add_wire(self, type: str, source: Optional[int], target: Optional[str]) -> Wire: 18 | if source is None: 19 | source = self.wire_id 20 | self.wire_id += 1 21 | wire_id = self.wire_id 22 | self.wire_id += 1 23 | wire = Wire(wire_id, type, source, target) 24 | self.add(wire) 25 | return wire 26 | 27 | def get_wire_id(self) -> int: 28 | value = self.wire_id 29 | self.wire_id += 1 30 | return value 31 | 32 | def get_wires(self, element: Union[Part, Call]) -> Optional[list[Wire]]: 33 | temp: list[Wire] = [] 34 | for child in self.children: 35 | if isinstance(child, Wire): 36 | for connection in child.get_connections(): 37 | if connection.source == element.id: 38 | temp.append(child) 39 | if len(temp) == 0: 40 | return None 41 | return temp -------------------------------------------------------------------------------- /tia_xml_generator/elements/section.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | from copy import deepcopy 5 | 6 | from tia_xml_generator.elements.member import Member 7 | 8 | 9 | class Section(XMLBase): 10 | element_name = "Section" 11 | 12 | members: list[Member] 13 | 14 | def __init__(self, name: str): 15 | super().__init__() 16 | self.members = [] 17 | self.element = ET.Element(self.element_name, {"Name": name}) 18 | 19 | def add_member(self, name: str, type: str) -> Member: 20 | if self.get_member(name) is not None: 21 | raise ValueError(f"Member {name} already exists in section {self.element.get('Name')}") 22 | member = Member(name, type) 23 | self.members.append(member) 24 | return member 25 | 26 | def get_member(self, name: str) -> Optional[Member]: 27 | for member in self.members: 28 | if member.name == name: 29 | return member 30 | return None 31 | 32 | def build(self) -> ET.Element: 33 | self_ = deepcopy(self) 34 | for member in self_.members: 35 | self_.add(member) 36 | 37 | self_.element.extend([child.build() for child in self_.children]) 38 | return self_.element 39 | 40 | def build_no_call(self) -> ET.Element: 41 | self_ = deepcopy(self) 42 | for member in self_.members: 43 | self_.add(member) 44 | 45 | self_.element.extend([child.build_no_call() for child in self_.children]) 46 | return self_.element 47 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/parts/part.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import Any, Optional 4 | from tia_xml_generator.elements.basis import XMLBase 5 | import xml.etree.ElementTree as ET 6 | 7 | 8 | class TemplateValue(XMLBase): 9 | element_name = "TemplateValue" 10 | 11 | def __init__(self, name: str, type: str, value: Any): 12 | super().__init__() 13 | self.element = ET.Element(self.element_name) 14 | self.element.set("Name", name) 15 | self.element.set("Type", type) 16 | self.element.text = str(value) 17 | 18 | 19 | class Part(XMLBase): 20 | element_name = "Part" 21 | 22 | def __init__(self, name: str, version: Optional[str], id: int): 23 | super().__init__() 24 | self.part_info_path = os.path.join(self.home_path, "parts.json") 25 | self.id: int = id 26 | self.name = name 27 | self.element = ET.Element(self.element_name) 28 | self.element.set("Name", name) 29 | 30 | if version is not None: 31 | self.element.set("Version", version) 32 | 33 | self.element.set("UId", str(id)) 34 | 35 | self.part_info = self.get_part_info(name, version) 36 | 37 | if self.part_info["multiple_outputs"]: 38 | self.add(TemplateValue("Card", "Cardinality", 1)) 39 | 40 | outputs = [] 41 | for i, output in enumerate(self.part_info["outputs"]): 42 | outputs.append(f"{output}{i+1}") 43 | 44 | self.part_info["outputs"] = outputs 45 | 46 | def get_part_info(self, name: str, version: Optional[str]) -> dict[str, list[str]]: 47 | with open(self.part_info_path, "r") as f: 48 | part_options = json.load(f) 49 | 50 | if version is None: 51 | return part_options[name]["None"] 52 | else: 53 | return part_options[name][version] 54 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/wire.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | 5 | 6 | class Connection(XMLBase): 7 | def __init__(self, type: str, source: Optional[int], target: Optional[str]): 8 | super().__init__() 9 | self.element = ET.Element(type) 10 | self.source = source 11 | self.target = target 12 | 13 | if source is not None: 14 | self.element.set("UId", str(source)) 15 | 16 | if target is not None: 17 | self.element.set("Name", target) 18 | 19 | 20 | class Wire(XMLBase): 21 | element_name = "Wire" 22 | 23 | def __init__(self, id: int, type: str, source: int, target: Optional[str]): 24 | super().__init__() 25 | self.element = ET.Element(self.element_name, {"UId": str(id)}) 26 | self.source = source 27 | self.add_connection(type, source, target) 28 | 29 | def add_connection(self, type: str, source: int, target: Optional[str] = None) -> Connection: 30 | connection = Connection(type, source, target) 31 | self.add(connection) 32 | return connection 33 | 34 | def add_powerrail(self) -> Connection: 35 | connection = Connection("Powerrail", None, None) 36 | self.add(connection) 37 | return connection 38 | 39 | def get_connections(self) -> list[Connection]: 40 | temp: list[Connection] = [] 41 | for child in self.children: 42 | if isinstance(child, Connection): 43 | temp.append(child) 44 | return temp 45 | 46 | def get_connections_description(self) -> str: 47 | connections = self.get_connections() 48 | if len(connections) == 0: 49 | return "" 50 | else: 51 | text = "" 52 | for i, connection in enumerate(connections): 53 | text += f"{i}: {connection.source} -> {connection.target}\n" 54 | return text 55 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/multilingual_text.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.attribute_list import AttributeList 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | 5 | from tia_xml_generator.elements.object_list import ObjectList 6 | 7 | class _MulitilingualTextItem(XMLBase): 8 | element_name = "MultilingualTextItem" 9 | 10 | def __init__(self, text: str, language: str): 11 | super().__init__() 12 | self.element = ET.Element(self.element_name, {"ID": self.global_id.next(), "CompositionName": "Items"}) 13 | self.load_attribute_list() 14 | self.__text = ET.Element("Text") 15 | self.__language = ET.Element("Culture") 16 | self.attribute_list.element.extend([self.__text, self.__language]) 17 | 18 | self.text = text 19 | self.language = language 20 | 21 | def load_attribute_list(self) -> None: 22 | self.attribute_list = AttributeList() 23 | self.add(self.attribute_list) 24 | 25 | @property 26 | def text(self) -> str: 27 | if self.__text.text is None: 28 | return "" 29 | return self.__text.text 30 | 31 | @text.setter 32 | def text(self, text: str) -> None: 33 | self.__text.text = text 34 | 35 | @property 36 | def language(self) -> str: 37 | if self.__language.text is None: 38 | return "" 39 | return self.__language.text 40 | 41 | @language.setter 42 | def language(self, language: str) -> None: 43 | self.__language.text = language 44 | 45 | class MultilingualText(XMLBase): 46 | element_name = "MultilingualText" 47 | 48 | def __init__(self, composition_name: str): 49 | super().__init__() 50 | self.element = ET.Element(self.element_name, {"ID": self.global_id.next(), "CompositionName": composition_name}) 51 | self.load_object_list() 52 | 53 | def load_object_list(self) -> None: 54 | self.object_list = ObjectList() 55 | self.add(self.object_list) 56 | 57 | def add_text(self, text: str, language: str = "en-US") -> None: 58 | self.object_list.add(_MulitilingualTextItem(text, language)) -------------------------------------------------------------------------------- /tia_xml_generator/elements/parts/call.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | 5 | 6 | class CallInstance(XMLBase): 7 | element_name = "Instance" 8 | 9 | def __init__(self, id: int, scope: str): 10 | super().__init__() 11 | self.element = ET.Element(self.element_name, {"UId": str(id), "Scope": scope}) 12 | 13 | def add_component(self, name: str) -> None: 14 | component = CallInstanceComponent(name) 15 | self.add(component) 16 | 17 | 18 | class CallInstanceComponent(XMLBase): 19 | element_name = "Component" 20 | 21 | def __init__(self, name: str): 22 | super().__init__() 23 | self.element = ET.Element(self.element_name, {"Name": name}) 24 | 25 | 26 | class CallInfo(XMLBase): 27 | element_name = "CallInfo" 28 | 29 | def __init__(self, name: str, type: Optional[str]): 30 | super().__init__() 31 | if type is None: 32 | self.element = ET.Element(self.element_name, {"Name": name}) 33 | else: 34 | self.element = ET.Element(self.element_name, {"Name": name, "BlockType": type}) 35 | 36 | def add_instance(self, id: int, scope: str, name: str) -> None: 37 | instance = CallInstance(id, scope) 38 | instance.add_component(name) 39 | self.add(instance) 40 | 41 | 42 | class Call(XMLBase): 43 | element_name = "Call" 44 | 45 | def __init__(self, id: int, name: str, block_type: Optional[str]): 46 | super().__init__() 47 | self.element = ET.Element(self.element_name, {"UId": str(id)}) 48 | self.id: int = id 49 | self.name = name 50 | 51 | self.load_call_info(name, block_type) 52 | 53 | def load_call_info(self, name: str, block_type: Optional[str]) -> CallInfo: 54 | self.call_info = CallInfo(name, block_type) 55 | self.add(self.call_info) 56 | return self.call_info 57 | 58 | def add_instance_db(self, id: int, name: str) -> None: 59 | self.call_info.add_instance(id, "GlobalVariable", name) 60 | 61 | def add_instance_variable(self, id: int, name: str) -> None: 62 | self.call_info.add_instance(id, "LocalVariable", name) 63 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/network_source.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | from copy import deepcopy 5 | 6 | from tia_xml_generator.elements.flg_net import FlgNet 7 | from tia_xml_generator.elements.parts.call import Call 8 | from tia_xml_generator.elements.parts.part import Part 9 | from tia_xml_generator.elements.wire import Wire 10 | 11 | 12 | class NetworkSource(XMLBase): 13 | element_name = "NetworkSource" 14 | 15 | def __init__(self): 16 | super().__init__() 17 | self.element = ET.Element(self.element_name) 18 | 19 | self.load_flg_net() 20 | 21 | def load_flg_net(self) -> None: 22 | self.flg_net = FlgNet() 23 | self.add(self.flg_net) 24 | 25 | def build(self) -> ET.Element: 26 | self_ = deepcopy(self) 27 | self_.flg_net.check_children() 28 | if len(self_.flg_net.children) == 0: 29 | self_.remove(self_.flg_net) 30 | self_.element.extend([child.build() for child in self_.children]) 31 | return self_.element 32 | 33 | def build_no_call(self) -> ET.Element: 34 | self_ = deepcopy(self) 35 | self_.flg_net.check_children_no_call() 36 | if len(self_.flg_net.children) == 0: 37 | self_.remove(self_.flg_net) 38 | self_.element.extend([child.build_no_call() for child in self_.children]) 39 | return self_.element 40 | 41 | def add_part(self, name: str, version: Optional[str]) -> Part: 42 | return self.flg_net.add_part(name, version) 43 | 44 | def get_part(self, name: str) -> Optional[list[Part]]: 45 | return self.flg_net.get_part(name) 46 | 47 | def add_call( 48 | self, 49 | name: str, 50 | current_block_type: str, 51 | block_type: Optional[str], 52 | reference: Optional[str], 53 | reference_type: Optional[str], 54 | ) -> Call: 55 | return self.flg_net.add_call(name, current_block_type, block_type, reference, reference_type) 56 | 57 | def get_call(self, name: str) -> Optional[list[Call]]: 58 | return self.flg_net.get_call(name) 59 | 60 | def add_wire(self, type: str, source: Optional[int], target: Optional[str]): 61 | return self.flg_net.add_wire(type, source, target) 62 | 63 | def get_wires(self, element: Union[Part, Call]) -> Optional[list[Wire]]: 64 | return self.flg_net.get_wires(element) 65 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/interface.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.basis import XMLBase 2 | 3 | import xml.etree.ElementTree as ET 4 | 5 | from tia_xml_generator.elements.section import Section 6 | 7 | 8 | class InterfaceSections(XMLBase): 9 | element_name = "Sections" 10 | 11 | def __init__(self): 12 | super().__init__() 13 | self.element = ET.Element( 14 | self.element_name, {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 15 | ) 16 | 17 | self.input = Section("Input") 18 | self.output = Section("Output") 19 | self.in_out = Section("InOut") 20 | self.static = Section("Static") 21 | self.temp = Section("Temp") 22 | self.constant = Section("Constant") 23 | 24 | self.add([self.input, self.output, self.in_out, self.static, self.temp, self.constant]) 25 | 26 | 27 | class InterfaceSectionsDB(XMLBase): 28 | element_name = "Sections" 29 | 30 | def __init__(self): 31 | super().__init__() 32 | self.element = ET.Element( 33 | self.element_name, {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 34 | ) 35 | 36 | self.static = Section("Static") 37 | 38 | self.add([self.static]) 39 | 40 | 41 | class InterfaceSectionsOB(XMLBase): 42 | element_name = "Sections" 43 | 44 | def __init__(self): 45 | super().__init__() 46 | self.element = ET.Element( 47 | self.element_name, {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 48 | ) 49 | 50 | self.input = Section("Input") 51 | self.temp = Section("Temp") 52 | self.constant = Section("Constant") 53 | 54 | self.add([self.input, self.temp, self.constant]) 55 | 56 | 57 | class Interface(XMLBase): 58 | element_name = "Interface" 59 | 60 | def __init__(self): 61 | super().__init__() 62 | self.element = ET.Element(self.element_name) 63 | self.load_sections() 64 | 65 | def load_sections(self) -> None: 66 | self.sections = InterfaceSections() 67 | self.add(self.sections) 68 | 69 | 70 | class InterfaceDB(XMLBase): 71 | element_name = "Interface" 72 | 73 | def __init__(self): 74 | super().__init__() 75 | self.element = ET.Element(self.element_name) 76 | self.load_sections() 77 | 78 | def load_sections(self) -> None: 79 | self.sections = InterfaceSectionsDB() 80 | self.add(self.sections) 81 | 82 | 83 | class InterfaceOB(XMLBase): 84 | element_name = "Interface" 85 | 86 | def __init__(self): 87 | super().__init__() 88 | self.element = ET.Element(self.element_name) 89 | self.load_sections() 90 | 91 | def load_sections(self) -> None: 92 | self.sections = InterfaceSectionsOB() 93 | self.add(self.sections) 94 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/member.py: -------------------------------------------------------------------------------- 1 | from tia_xml_generator.elements.attribute_list import AttributeList 2 | from tia_xml_generator.elements.basis import XMLBase 3 | import xml.etree.ElementTree as ET 4 | 5 | from tia_xml_generator.elements.comment import Comment 6 | 7 | 8 | class AttributeListMember(AttributeList): 9 | __external_accessible = ET.Element("BooleanAttribute", {"Name": "ExternalAccessible", "SystemDefined": "true"}) 10 | __external_visible = ET.Element("BooleanAttribute", {"Name": "ExternalVisible", "SystemDefined": "true"}) 11 | __external_writable = ET.Element("BooleanAttribute", {"Name": "ExternalWritable", "SystemDefined": "true"}) 12 | 13 | def __init__( 14 | self, external_accessible: bool = False, external_visible: bool = False, external_writable: bool = False 15 | ): 16 | super().__init__() 17 | self.element.extend([self.__external_accessible, self.__external_visible, self.__external_writable]) 18 | self.external_accessible = external_accessible 19 | self.external_visible = external_visible 20 | self.external_writable = external_writable 21 | 22 | @property 23 | def external_accessible(self) -> bool: 24 | if self.__external_accessible.text is None: 25 | return False 26 | return self.__external_accessible.text == "true" 27 | 28 | @external_accessible.setter 29 | def external_accessible(self, external_accessible: bool) -> None: 30 | self.__external_accessible.text = "true" if external_accessible else "false" 31 | 32 | @property 33 | def external_visible(self) -> bool: 34 | if self.__external_visible.text is None: 35 | return False 36 | return self.__external_visible.text == "true" 37 | 38 | @external_visible.setter 39 | def external_visible(self, external_visible: bool) -> None: 40 | self.__external_visible.text = "true" if external_visible else "false" 41 | 42 | @property 43 | def external_writable(self) -> bool: 44 | if self.__external_writable.text is None: 45 | return False 46 | return self.__external_writable.text == "true" 47 | 48 | @external_writable.setter 49 | def external_writable(self, external_writable: bool) -> None: 50 | self.__external_writable.text = "true" if external_writable else "false" 51 | 52 | 53 | class Member(XMLBase): 54 | element_name = "Member" 55 | 56 | __comment = None 57 | 58 | def __init__(self, name: str, data_type: str): 59 | super().__init__() 60 | self.name = name 61 | self.data_type = data_type 62 | self.element = ET.Element(self.element_name, {"Name": name, "Datatype": data_type}) 63 | self.load_attribute_list() 64 | 65 | def load_attribute_list(self) -> None: 66 | self.attribute_list = AttributeListMember() 67 | self.add(self.attribute_list) 68 | 69 | def add_comment(self, comment: str, language: str = "en-US") -> None: 70 | if self.__comment is None: 71 | self.__comment = Comment(comment, language) 72 | self.add(self.__comment) 73 | else: 74 | self.__comment.add(comment, language) 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | *venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # PyCharm 148 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 149 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 150 | # and can be added to the global gitignore or merged into this file. For a more nuclear 151 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 152 | #.idea/ 153 | 154 | # Custom 155 | *.bak 156 | data/ 157 | test.py 158 | setup.py -------------------------------------------------------------------------------- /tia_xml_generator/elements/basis.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, Optional, Protocol, Self, Union 3 | import xml.etree.ElementTree as ET 4 | from copy import deepcopy 5 | 6 | 7 | class ID: 8 | """Global ID class.""" 9 | 10 | id: int 11 | 12 | def __init__(self) -> None: 13 | self.id = 0 14 | 15 | def next(self) -> str: 16 | r_value = self.id 17 | self.id += 1 18 | 19 | if r_value == 0: 20 | return str(0) 21 | else: 22 | return hex(r_value).lstrip("0x").rstrip("L").upper() 23 | 24 | def reset(self, value: Optional[int] = None): 25 | if value is None: 26 | self.id = 0 27 | else: 28 | self.id = value 29 | 30 | 31 | class XMLElement(Protocol): 32 | element_name: str 33 | """The name of the XML element.""" 34 | element: ET.Element 35 | """The XML element.""" 36 | id: Optional[Any] 37 | """The ID of the XML element.""" 38 | children: list[Self] 39 | 40 | template_path: str 41 | """The path to the templates.""" 42 | export_path: str 43 | """The path to the exports.""" 44 | home_path: str 45 | """The path to the home directory.""" 46 | 47 | def add(self, child: Union[list[Self], Self]) -> None: 48 | """Adds a child element to the XML element.""" 49 | ... 50 | 51 | def remove(self, child: Union[list[Self], Self]) -> None: 52 | """Removes a child element from the XML element.""" 53 | ... 54 | 55 | def build(self) -> ET.Element: 56 | """Builds the XML element.""" 57 | ... 58 | 59 | def build_no_call(self) -> ET.Element: 60 | """Builds the XML element without the call.""" 61 | ... 62 | 63 | 64 | class XMLBase(XMLElement): 65 | """Protocol for all basis classes.""" 66 | 67 | element_name: str 68 | """The name of the XML element.""" 69 | element: ET.Element 70 | """The XML element.""" 71 | children: list[XMLElement] 72 | """The child elements of the XML element.""" 73 | id: Optional[Union[str, int]] 74 | """The ID of the XML element.""" 75 | global_id: ID = ID() 76 | 77 | template_path: str = os.path.join(os.path.expanduser("~"), ".tia_portal", "templates") 78 | export_path: str = os.path.join(os.path.expanduser("~"), ".tia_portal", "exports") 79 | import_path: str = os.path.join(os.path.expanduser("~"), ".tia_portal", "imports") 80 | temp_path: str = os.path.join(os.path.expanduser("~"), ".tia_portal", "temp") 81 | home_path: str = os.path.join(os.path.expanduser("~"), ".tia_portal") 82 | 83 | def __init__(self) -> None: 84 | self.children = [] 85 | 86 | def build(self) -> ET.Element: 87 | """Builds the XML element.""" 88 | self_ = deepcopy(self) 89 | self_.element.extend([child.build() for child in self_.children]) 90 | return self_.element 91 | 92 | def build_no_call(self) -> ET.Element: 93 | """Builds the XML element without the call.""" 94 | self_ = deepcopy(self) 95 | self_.element.extend([child.build_no_call() for child in self_.children]) 96 | return self_.element 97 | 98 | def add(self, child: Union[list[XMLElement], XMLElement]) -> None: 99 | """Adds a child element to the XML element.""" 100 | if isinstance(child, list): 101 | self.children.extend(child) 102 | else: 103 | self.children.append(child) 104 | 105 | def remove(self, child: XMLElement) -> None: 106 | """Removes a child element from the XML element.""" 107 | self.children.remove(child) 108 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/compile_unit.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from tia_xml_generator.elements.attribute_list import AttributeList 3 | from tia_xml_generator.elements.basis import XMLBase 4 | import xml.etree.ElementTree as ET 5 | from tia_xml_generator.elements.multilingual_text import MultilingualText 6 | from tia_xml_generator.elements.network_source import NetworkSource 7 | from tia_xml_generator.elements.parts.call import Call 8 | from tia_xml_generator.elements.parts.part import Part 9 | from tia_xml_generator.elements.wire import Wire 10 | 11 | from tia_xml_generator.enums import ProgrammingLanguage 12 | from tia_xml_generator.elements.object_list import ObjectList 13 | 14 | 15 | class AttributeListCompileUnit(AttributeList): 16 | def __init__(self): 17 | super().__init__() 18 | self.__programming_language = ET.Element("ProgrammingLanguage") 19 | 20 | @property 21 | def programming_language(self) -> Optional[ProgrammingLanguage]: 22 | if self.__programming_language.text is None: 23 | return None 24 | return ProgrammingLanguage[self.__programming_language.text] 25 | 26 | @programming_language.setter 27 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 28 | self.__programming_language.text = programming_language.name 29 | 30 | 31 | class ObjectListCompileUnit(ObjectList): 32 | def __init__(self, name: str, description: str): 33 | super().__init__() 34 | self.__title = MultilingualText("Title") 35 | self.__comment = MultilingualText("Comment") 36 | 37 | self.add(self.__title) 38 | self.add(self.__comment) 39 | 40 | self.__title.add_text(name) 41 | self.__comment.add_text(description) 42 | 43 | 44 | class CompileUnit(XMLBase): 45 | element_name = "SW.Blocks.CompileUnit" 46 | 47 | def __init__(self, title: str, programming_language: ProgrammingLanguage, comment: str = ""): 48 | super().__init__() 49 | self.name = title 50 | self.element = ET.Element(self.element_name, {"ID": self.global_id.next(), "CompositionName": "CompileUnits"}) 51 | self.description = comment 52 | 53 | self.load_attribute_list() 54 | self.programming_language = programming_language 55 | self.load_network_source() 56 | self.load_object_list(title, comment) 57 | 58 | def load_attribute_list(self) -> None: 59 | self.attribute_list = AttributeListCompileUnit() 60 | self.add(self.attribute_list) 61 | 62 | def load_network_source(self) -> None: 63 | self.network_source = NetworkSource() 64 | self.attribute_list.add(self.network_source) 65 | 66 | def load_object_list(self, title: str, comment: str) -> None: 67 | self.object_list = ObjectListCompileUnit(title, comment) 68 | self.add(self.object_list) 69 | 70 | def add_part(self, name: str, version: Optional[str] = None) -> Part: 71 | return self.network_source.add_part(name, version) 72 | 73 | def add_call( 74 | self, 75 | name: str, 76 | current_block_type: str, 77 | block_type: Optional[str] = None, 78 | reference: Optional[str] = None, 79 | reference_type: Optional[str] = None, 80 | ) -> Call: 81 | return self.network_source.add_call(name, current_block_type, block_type, reference, reference_type) 82 | 83 | def add_wire(self, type: str, source: Optional[int], target: Optional[str]): 84 | return self.network_source.add_wire(type, source, target) 85 | 86 | def get_part(self, name: str) -> Optional[list[Part]]: 87 | return self.network_source.get_part(name) 88 | 89 | def get_call(self, name: str) -> Optional[list[Call]]: 90 | return self.network_source.get_call(name) 91 | 92 | def get_wires(self, element: Union[Part, Call]) -> Optional[list[Wire]]: 93 | return self.network_source.get_wires(element) 94 | 95 | @property 96 | def programming_language(self) -> Optional[ProgrammingLanguage]: 97 | return self.attribute_list.programming_language 98 | 99 | @programming_language.setter 100 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 101 | self.attribute_list.programming_language = programming_language 102 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/flg_net.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from typing import Optional, Union 3 | from tia_xml_generator.elements.basis import XMLBase 4 | import xml.etree.ElementTree as ET 5 | 6 | from tia_xml_generator.elements.parts import Parts 7 | from tia_xml_generator.elements.parts.call import Call 8 | from tia_xml_generator.elements.parts.part import Part 9 | from tia_xml_generator.elements.wire import Wire 10 | from tia_xml_generator.elements.wires import Wires 11 | 12 | 13 | class FlgNet(XMLBase): 14 | element_name = "FlgNet" 15 | 16 | def __init__(self): 17 | super().__init__() 18 | self.element = ET.Element( 19 | self.element_name, {"xmlns": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v3"} 20 | ) 21 | 22 | self.load_parts() 23 | self.load_wires() 24 | 25 | def load_parts(self) -> None: 26 | self.parts = Parts() 27 | self.add(self.parts) 28 | 29 | def load_wires(self) -> None: 30 | self.wires = Wires() 31 | self.add(self.wires) 32 | 33 | def check_children(self) -> None: 34 | if len(self.parts.children) == 0: 35 | if self.children.count(self.parts) == 1: 36 | self.remove(self.parts) 37 | if len(self.wires.children) == 0: 38 | if self.children.count(self.wires) == 1: 39 | self.remove(self.wires) 40 | 41 | def check_children_no_call(self) -> None: 42 | for child in self.parts.children: 43 | if isinstance(child, Call): 44 | wires = self.wires.get_wires(child) 45 | if not wires is None: 46 | for wire in wires: 47 | self.wires.remove(wire) 48 | self.parts.remove(child) 49 | 50 | if len(self.parts.children) == 0: 51 | if self.children.count(self.parts) == 1: 52 | self.remove(self.parts) 53 | if len(self.wires.children) == 0: 54 | if self.children.count(self.wires) == 1: 55 | self.remove(self.wires) 56 | 57 | def build(self) -> ET.Element: 58 | self_ = deepcopy(self) 59 | self_.check_children() 60 | self_.element.extend([child.build() for child in self_.children]) 61 | return self_.element 62 | 63 | def build_no_call(self) -> ET.Element: 64 | self_ = deepcopy(self) 65 | self_.check_children_no_call() 66 | self_.element.extend([child.build_no_call() for child in self_.children]) 67 | return self_.element 68 | 69 | def add_part(self, name: str, version: Optional[str]) -> Part: 70 | if self.parts.part_id < self.wires.wire_id: 71 | self.parts.part_id = self.wires.wire_id 72 | 73 | part = self.parts.add_part(name, version) 74 | 75 | for input_value in part.part_info["inputs"]: 76 | wire = self.add_wire("NameCon", part.id, input_value) 77 | wire.add_connection("OpenCon", self.wires.get_wire_id()) 78 | 79 | for output in part.part_info["outputs"]: 80 | wire = self.add_wire("NameCon", part.id, output) 81 | wire.add_connection("OpenCon", self.wires.get_wire_id()) 82 | 83 | return part 84 | 85 | def get_part(self, name: str) -> Optional[list[Part]]: 86 | return self.parts.get_part(name) 87 | 88 | def add_call( 89 | self, 90 | name: str, 91 | current_block_type: str, 92 | block_type: Optional[str], 93 | reference: Optional[str], 94 | reference_type: Optional[str], 95 | ) -> Call: 96 | if self.parts.part_id < self.wires.wire_id: 97 | self.parts.part_id = self.wires.wire_id 98 | 99 | call = self.parts.add_call(name, block_type, reference, reference_type) 100 | 101 | wire = self.add_wire("NameCon", call.id, "en") 102 | if current_block_type == "FB": 103 | wire.add_connection("OpenCon", self.wires.get_wire_id()) 104 | elif current_block_type == "OB": 105 | wire.add_powerrail() 106 | 107 | return call 108 | 109 | def get_call(self, name: str) -> Optional[list[Call]]: 110 | return self.parts.get_call(name) 111 | 112 | def add_wire(self, wire_type: str, source: Optional[int], target: Optional[str]) -> Wire: 113 | if self.wires.wire_id < self.parts.part_id: 114 | self.wires.wire_id = self.parts.part_id 115 | return self.wires.add_wire(wire_type, source, target) 116 | 117 | def get_wires(self, element: Union[Part, Call]) -> Optional[list[Wire]]: 118 | return self.wires.get_wires(element) 119 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/parts/__init__.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | import os 3 | from typing import Any, Optional 4 | from tia_xml_generator.elements.basis import XMLBase 5 | import xml.etree.ElementTree as ET 6 | import json 7 | from tia_xml_generator.elements.parts.call import Call 8 | 9 | from tia_xml_generator.elements.parts.part import Part 10 | 11 | 12 | class Parts(XMLBase): 13 | element_name = "Parts" 14 | 15 | def __init__(self): 16 | super().__init__() 17 | self.path = os.path.join(self.home_path, "parts.json") 18 | self.element = ET.Element(self.element_name) 19 | self.part_id = 21 20 | self.part_options: dict[str, Any] = {} 21 | 22 | if not os.path.exists(self.path): 23 | os.makedirs(os.path.join(os.path.dirname(self.path), "parts"), exist_ok=True) 24 | with open(self.path, "w") as f: 25 | f.write(json.dumps({})) 26 | 27 | with open(self.path, "r") as f: 28 | self.part_options = json.load(f) 29 | 30 | def add_part(self, name: str, version: Optional[str] = None) -> Part: 31 | part_not_found = False 32 | if version is None: 33 | if not self.part_options.get(name, None) is None: 34 | part = Part(name, version, self.part_id) 35 | self.add(part) 36 | self.part_id += 1 37 | return part 38 | else: 39 | part_not_found = True 40 | else: 41 | if not self.part_options.get(name, {}).get(version, None) is None: 42 | part = Part(name, version, self.part_id) 43 | self.add(part) 44 | self.part_id += 1 45 | return part 46 | else: 47 | part_not_found = True 48 | 49 | if part_not_found: 50 | print(f"Part '{name}' is not defined") 51 | information: dict[str, Any] = {"inputs": [], "outputs": []} 52 | 53 | while True: 54 | new_input = input("Give the name of the input (send q to exit): ") 55 | if new_input == "q": 56 | break 57 | information["inputs"].append(new_input.strip()) 58 | 59 | multiple_out = input("Is it possible to add more outputs (y/n): ") 60 | if multiple_out == "y": 61 | information["multiple_outputs"] = True 62 | else: 63 | information["multiple_outputs"] = False 64 | 65 | while True: 66 | new_output = input("Give the name of the output (send q to exit): ") 67 | if new_output == "q": 68 | break 69 | information["outputs"].append(new_output.strip()) 70 | 71 | if not self.part_options.get(name, None) is None: 72 | if version is None: 73 | self.part_options[name]["None"] = information 74 | else: 75 | self.part_options[name][version] = information 76 | else: 77 | if version is None: 78 | self.part_options[name] = {"None": information} 79 | else: 80 | self.part_options[name] = {version: information} 81 | 82 | with open(self.path, "w") as f: 83 | f.write(json.dumps(self.part_options)) 84 | 85 | part = Part(name, version, self.part_id) 86 | self.add(part) 87 | 88 | self.part_id += 1 89 | return part 90 | else: 91 | raise Exception("Something went wrong, very wrong") 92 | 93 | def get_part(self, name: str) -> Optional[list[Part]]: 94 | temp: list[Part] = [] 95 | for child in self.children: 96 | if isinstance(child, Part): 97 | if child.name == name: 98 | temp.append(child) 99 | if len(temp) == 0: 100 | return None 101 | else: 102 | return temp 103 | 104 | def add_call( 105 | self, name: str, block_type: Optional[str], reference: Optional[str], reference_type: Optional[str] 106 | ) -> Call: 107 | call = Call(self.part_id, name, block_type) 108 | self.part_id += 1 109 | if not reference is None and not reference_type is None: 110 | if reference_type == "DB": 111 | call.add_instance_db(self.part_id, reference) 112 | elif reference_type == "Variable": 113 | call.add_instance_variable(self.part_id, reference) 114 | else: 115 | raise ValueError(f"Reference type '{reference_type}' is not supported") 116 | self.part_id += 1 117 | self.add(call) 118 | return call 119 | 120 | def get_call(self, name: str) -> Optional[list[Call]]: 121 | temp: list[Call] = [] 122 | for child in self.children: 123 | if isinstance(child, Call): 124 | if child.name == name: 125 | temp.append(child) 126 | if len(temp) == 0: 127 | return None 128 | else: 129 | return temp 130 | 131 | def build_no_call(self) -> ET.Element: 132 | self_ = deepcopy(self) 133 | self_.element.extend([child.build_no_call() for child in self.children if not isinstance(child, Call)]) 134 | return self_.element 135 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/blocks/db.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import xml.etree.ElementTree as ET 3 | from tia_xml_generator.elements.multilingual_text import MultilingualText 4 | from tia_xml_generator.enums import ProgrammingLanguage 5 | from tia_xml_generator.elements.attribute_list import AttributeList 6 | 7 | from tia_xml_generator.elements.basis import XMLBase 8 | from tia_xml_generator.elements.blocks import Block 9 | from tia_xml_generator.elements.interface import InterfaceDB 10 | from tia_xml_generator.elements.member import Member 11 | from tia_xml_generator.elements.object_list import ObjectList 12 | 13 | 14 | class AttributeListDB(AttributeList): 15 | def __init__(self): 16 | super().__init__() 17 | self.__name = ET.Element("Name") 18 | self.__auto_number = ET.Element("AutoNumber") 19 | self.__author = ET.Element("HeaderAuthor") 20 | self.__family = ET.Element("HeaderFamily") 21 | self.__header_name = ET.Element("HeaderName") 22 | self.__version = ET.Element("HeaderVersion") 23 | self.__memory_layout = ET.Element("MemoryLayout") 24 | self.__number = ET.Element("Number") 25 | self.__programming_language = ET.Element("ProgrammingLanguage") 26 | # TODO: Add all attributes 27 | self.__assigned_prodiag_fb = ET.Element("AssignedProDiagFB") 28 | self.__supervisions = ET.Element("Supervisions") 29 | self.__is_iec_check_enabled = ET.Element("IsIECCheckEnabled") 30 | self.__is_retain_mem_res_enabled = ET.Element("IsRetainMemResEnabled") 31 | self.__memory_reserve = ET.Element("MemoryReserve") 32 | self.__retain_memory_reserve = ET.Element("RetainMemoryReserve") 33 | self.__parameter_passing = ET.Element("ParameterPassing") 34 | self.__uda_block_properties = ET.Element("UDABlockProperties") 35 | self.__uda_enable_tag_readback = ET.Element("UDAEnableTagReadback") 36 | self.__library_conformance_status = ET.Element("LibraryConformanceStatus") 37 | self.element.extend([self.__name, self.__programming_language]) 38 | self.auto_number = True 39 | self.load_interface() 40 | 41 | def load_interface(self) -> None: 42 | self.interface = InterfaceDB() 43 | self.add(self.interface) 44 | 45 | @property 46 | def name(self) -> str: 47 | if self.__name.text is None: 48 | return "" 49 | return self.__name.text 50 | 51 | @name.setter 52 | def name(self, name: str) -> None: 53 | self.__name.text = name 54 | 55 | @property 56 | def programming_language(self) -> Optional[ProgrammingLanguage]: 57 | if self.__programming_language.text is None: 58 | return None 59 | return ProgrammingLanguage[self.__programming_language.text] 60 | 61 | @programming_language.setter 62 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 63 | self.__programming_language.text = programming_language.name 64 | 65 | @property 66 | def auto_number(self) -> bool: 67 | if self.__auto_number.text is None: 68 | return False 69 | return self.__auto_number.text == "true" 70 | 71 | @auto_number.setter 72 | def auto_number(self, auto_number: bool) -> None: 73 | self.__auto_number.text = "true" if auto_number else "false" 74 | if self.element.find("AutoNumber") is None: 75 | self.element.append(self.__auto_number) 76 | 77 | if auto_number and self.element.find("Number") is not None: 78 | self.element.remove(self.__number) 79 | 80 | if not auto_number and self.number != "": 81 | self.element.append(self.__number) 82 | 83 | if not auto_number and self.number == "": 84 | raise ValueError("Number must be set if auto_number is False") 85 | 86 | @property 87 | def assigned_prodiag_fb(self) -> str: 88 | if self.__assigned_prodiag_fb.text is None: 89 | return "" 90 | return self.__assigned_prodiag_fb.text 91 | 92 | @assigned_prodiag_fb.setter 93 | def assigned_prodiag_fb(self, assigned_prodiag_fb: str) -> None: 94 | self.__assigned_prodiag_fb.text = assigned_prodiag_fb 95 | if self.element.find("AssignedProDiagFB") is None: 96 | self.element.append(self.__assigned_prodiag_fb) 97 | 98 | @property 99 | def author(self) -> str: 100 | if self.__author.text is None: 101 | return "" 102 | return self.__author.text 103 | 104 | @author.setter 105 | def author(self, author: str) -> None: 106 | self.__author.text = author 107 | if self.element.find("HeaderAuthor") is None: 108 | self.element.append(self.__author) 109 | 110 | @property 111 | def family(self) -> str: 112 | if self.__family.text is None: 113 | return "" 114 | return self.__family.text 115 | 116 | @family.setter 117 | def family(self, family: str) -> None: 118 | self.__family.text = family 119 | if self.element.find("HeaderFamily") is None: 120 | self.element.append(self.__family) 121 | 122 | @property 123 | def header_name(self) -> str: 124 | if self.__header_name.text is None: 125 | return "" 126 | return self.__header_name.text 127 | 128 | @header_name.setter 129 | def header_name(self, header_name: str) -> None: 130 | self.__header_name.text = header_name 131 | if self.element.find("HeaderName") is None: 132 | self.element.append(self.__header_name) 133 | 134 | @property 135 | def version(self) -> str: 136 | if self.__version.text is None: 137 | return "" 138 | return self.__version.text 139 | 140 | @version.setter 141 | def version(self, version: str) -> None: 142 | self.__version.text = version 143 | if self.element.find("HeaderVersion") is None: 144 | self.element.append(self.__version) 145 | 146 | @property 147 | def memory_layout(self) -> str: 148 | if self.__memory_layout.text is None: 149 | return "" 150 | return self.__memory_layout.text 151 | 152 | @memory_layout.setter 153 | def memory_layout(self, memory_layout: str) -> None: 154 | self.__memory_layout.text = memory_layout 155 | if self.element.find("MemoryLayout") is None: 156 | self.element.append(self.__memory_layout) 157 | 158 | @property 159 | def number(self) -> str: 160 | if self.__number.text is None: 161 | return "" 162 | return self.__number.text 163 | 164 | @number.setter 165 | def number(self, number: str) -> None: 166 | self.__number.text = number 167 | if self.element.find("Number") is None and self.auto_number is False: 168 | self.element.append(self.__number) 169 | 170 | 171 | class ObjectListDB(ObjectList): 172 | def __init__(self): 173 | super().__init__() 174 | self.title = MultilingualText("Title") 175 | self.comment = MultilingualText("Comment") 176 | 177 | self.add(self.title) 178 | self.add(self.comment) 179 | 180 | self.title.add_text("") 181 | self.comment.add_text("") 182 | 183 | 184 | class DB(XMLBase, Block): 185 | element_name = "SW.Blocks.GlobalDB" 186 | attribute_list: AttributeListDB 187 | 188 | def __init__(self, name: str): 189 | super().__init__() 190 | self.load_attribute_list() 191 | self.id = self.global_id.next() 192 | self.name = name 193 | self.programming_language = ProgrammingLanguage.DB 194 | self.element = ET.Element(self.element_name, {"ID": self.id}) 195 | self.load_object_list() 196 | 197 | def load_attribute_list(self) -> None: 198 | self.attribute_list = AttributeListDB() 199 | self.add(self.attribute_list) 200 | 201 | def load_object_list(self) -> None: 202 | self.object_list = ObjectListDB() 203 | self.add(self.object_list) 204 | 205 | def add_static(self, name: str, data_type: str) -> Member: 206 | if self.get_static(name) is not None: 207 | raise ValueError(f"Static member {name} already exists") 208 | return self.attribute_list.interface.sections.static.add_member(name, data_type) 209 | 210 | def get_static(self, name: str) -> Optional[Member]: 211 | return self.attribute_list.interface.sections.static.get_member(name) 212 | 213 | @property 214 | def author(self) -> str: 215 | return self.attribute_list.author 216 | 217 | @author.setter 218 | def author(self, author: str) -> None: 219 | self.attribute_list.author = author 220 | 221 | @property 222 | def family(self) -> str: 223 | return self.attribute_list.family 224 | 225 | @family.setter 226 | def family(self, family: str) -> None: 227 | self.attribute_list.family = family 228 | 229 | @property 230 | def header_name(self) -> str: 231 | return self.attribute_list.header_name 232 | 233 | @header_name.setter 234 | def header_name(self, header_name: str) -> None: 235 | self.attribute_list.header_name = header_name 236 | 237 | @property 238 | def version(self) -> str: 239 | return self.attribute_list.version 240 | 241 | @version.setter 242 | def version(self, version: str) -> None: 243 | self.attribute_list.version = version 244 | 245 | @property 246 | def memory_layout(self) -> str: 247 | return self.attribute_list.memory_layout 248 | 249 | @memory_layout.setter 250 | def memory_layout(self, memory_layout: str) -> None: 251 | self.attribute_list.memory_layout = memory_layout 252 | 253 | @property 254 | def auto_number(self) -> bool: 255 | return self.attribute_list.auto_number 256 | 257 | @auto_number.setter 258 | def auto_number(self, auto_number: bool) -> None: 259 | self.attribute_list.auto_number = auto_number 260 | 261 | @property 262 | def assigned_prodiag_fb(self) -> str: 263 | return self.attribute_list.assigned_prodiag_fb 264 | 265 | @assigned_prodiag_fb.setter 266 | def assigned_prodiag_fb(self, assigned_prodiag_fb: str) -> None: 267 | self.attribute_list.assigned_prodiag_fb = assigned_prodiag_fb 268 | 269 | @property 270 | def number(self) -> str: 271 | return self.attribute_list.number 272 | 273 | @number.setter 274 | def number(self, number: str) -> None: 275 | self.attribute_list.number = number 276 | 277 | @property 278 | def programming_language(self) -> Optional[ProgrammingLanguage]: 279 | return self.attribute_list.programming_language 280 | 281 | @programming_language.setter 282 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 283 | self.attribute_list.programming_language = programming_language 284 | 285 | @property 286 | def name(self) -> str: 287 | return self.attribute_list.name 288 | 289 | @name.setter 290 | def name(self, name: str) -> None: 291 | self.attribute_list.name = name 292 | 293 | @property 294 | def statics(self) -> list[Member]: 295 | return self.attribute_list.interface.sections.static.members 296 | 297 | @statics.setter 298 | def statics(self, statics: list[Member]) -> None: 299 | raise AttributeError("Statics cannot be set") 300 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/document.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | import pickle 4 | from typing import Self 5 | import xml.etree.ElementTree as ET 6 | from tia_xml_generator.enums import ProgrammingLanguage, SecondaryType 7 | from tia_xml_generator.elements.basis import XMLBase 8 | from tia_xml_generator.elements.blocks.fb import FB 9 | from tia_xml_generator.elements.blocks.db import DB 10 | from tia_xml_generator.elements.blocks.ob import OB 11 | 12 | 13 | class Document(XMLBase): 14 | element_name = "Document" 15 | 16 | def __init__(self) -> None: 17 | super().__init__() 18 | self.id = None 19 | self.global_id_saved = None 20 | self.element = ET.Element(self.element_name) 21 | self.load_document_info() 22 | 23 | if not os.path.exists(self.template_path): 24 | os.mkdir(self.template_path) 25 | 26 | if not os.path.exists(self.import_path): 27 | os.mkdir(self.import_path) 28 | 29 | if not os.path.exists(self.temp_path): 30 | os.mkdir(self.temp_path) 31 | 32 | @property 33 | def fbs(self) -> list[FB]: 34 | """Returns all FBs in the document.""" 35 | return [child for child in self.children if isinstance(child, FB)] 36 | 37 | @property 38 | def dbs(self) -> list[DB]: 39 | """Returns all DBs in the document.""" 40 | return [child for child in self.children if isinstance(child, DB)] 41 | 42 | @property 43 | def obs(self) -> list[OB]: 44 | """Returns all OBs in the document.""" 45 | return [child for child in self.children if isinstance(child, OB)] 46 | 47 | def load_document_info(self) -> None: 48 | """Loads the document info.""" 49 | document_info = ET.SubElement(self.element, "DocumentInfo") 50 | document_info_created = ET.SubElement(document_info, "Created") 51 | document_info_created.text = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") 52 | document_info_export_settings = ET.SubElement(document_info, "ExportSettings") 53 | document_info_export_settings.text = "None" 54 | 55 | # It is not necessary to add the InstalledProducts to the DocumentInfo 56 | 57 | def add_fb(self, name: str, programming_language: ProgrammingLanguage, description: str) -> FB: 58 | """Adds a FB to the document.""" 59 | fb = FB(name, programming_language, description) 60 | self.add(fb) 61 | return fb 62 | 63 | def get_fb(self, name: str) -> FB: 64 | """Returns a FB from the document.""" 65 | for child in self.children: 66 | if isinstance(child, FB): 67 | if child.name == name: 68 | return child 69 | 70 | raise ValueError(f"The FB '{name}' does not exist in the document.") 71 | 72 | def add_db(self, name: str) -> DB: 73 | """Adds a DB to the document.""" 74 | db = DB(name) 75 | self.add(db) 76 | return db 77 | 78 | def get_db(self, name: str) -> DB: 79 | """Returns a DB from the document.""" 80 | for child in self.children: 81 | if isinstance(child, DB): 82 | if child.name == name: 83 | return child 84 | 85 | raise ValueError(f"The DB '{name}' does not exist in the document.") 86 | 87 | def add_ob( 88 | self, name: str, programming_language: ProgrammingLanguage, description: str, secondary_type: SecondaryType 89 | ) -> OB: 90 | """Adds a OB to the document.""" 91 | ob = OB(name, programming_language, description, secondary_type) 92 | self.add(ob) 93 | return ob 94 | 95 | def get_ob(self, name: str) -> OB: 96 | """Returns a OB from the document.""" 97 | for child in self.children: 98 | if isinstance(child, OB): 99 | if child.name == name: 100 | return child 101 | 102 | raise ValueError(f"The OB '{name}' does not exist in the document.") 103 | 104 | def save_template(self, name: str, overwrite: bool = False, temp: bool = False) -> None: 105 | """Saves the document as a template.""" 106 | self.global_id_saved = self.global_id.id 107 | if not temp: 108 | path = os.path.join(self.template_path, f"{name}.pkl") 109 | else: 110 | path = os.path.join(self.temp_path, f"{name}.pkl") 111 | 112 | if os.path.exists(path) and not (overwrite or temp): 113 | raise FileExistsError(f"The file '{path}' already exists.") 114 | 115 | with open(path, "wb") as f: 116 | pickle.dump(self, f, pickle.HIGHEST_PROTOCOL) 117 | 118 | @classmethod 119 | def load_template(cls, name: str, temp: bool = False) -> Self: 120 | """Loads a template.""" 121 | if not name.endswith(".pkl"): 122 | name = f"{name}.pkl" 123 | 124 | if not os.path.isfile(name): 125 | if not temp: 126 | path = os.path.join(cls.template_path, name) 127 | else: 128 | path = os.path.join(cls.temp_path, name) 129 | 130 | if not os.path.exists(path) and not temp: 131 | path = os.path.join(cls.temp_path, name) 132 | 133 | if not os.path.exists(path) and temp: 134 | path = os.path.join(cls.template_path, name) 135 | 136 | if not os.path.exists(path): 137 | raise FileNotFoundError(f"The file '{path}' does not exist.") 138 | else: 139 | path = name 140 | 141 | with open(path, "rb") as f: 142 | doc: Self = pickle.load(f) 143 | 144 | if not os.path.exists(doc.template_path): 145 | os.mkdir(doc.template_path) 146 | 147 | if not os.path.exists(doc.import_path): 148 | os.mkdir(doc.import_path) 149 | 150 | doc.global_id.reset(doc.global_id_saved) 151 | doc.global_id_saved = None 152 | 153 | return doc 154 | 155 | # @classmethod 156 | # def load_from_file(cls, path: str) -> Self: 157 | # """Loads a document from a file.""" 158 | # if not path.endswith(".xml"): 159 | # path = f"{path}.xml" 160 | # if not os.path.exists(path): 161 | # raise FileNotFoundError(f"The file '{path}' does not exist.") 162 | 163 | # tree = ET.parse(path) 164 | # root = tree.getroot() 165 | # if root is None: 166 | # raise ValueError(f"The file '{path}' does not contain a document.") 167 | # document = cls() 168 | 169 | # for child in root: 170 | # if child.tag == "DocumentInfo": 171 | # old_document_info = document.element.find("DocumentInfo") 172 | # if not old_document_info is None: 173 | # document.element.remove(old_document_info) 174 | # document.element.append(child) 175 | # elif child.tag == "SW.Blocks.FB": 176 | # fb_name = child.find("AttributeList/Name") 177 | # fb_name = fb_name.text if not fb_name is None else None 178 | # if fb_name is None: 179 | # raise ValueError("The FB does not have a name.") 180 | # fb_language = child.find("AttributeList/ProgrammingLanguage") 181 | # fb_language = fb_language.text if not fb_language is None else None 182 | # if fb_language is None: 183 | # raise ValueError("The FB does not have a programming language.") 184 | # fb_language = ProgrammingLanguage[fb_language] 185 | # fb_description = child.find( 186 | # "ObjectList/MultilingualText[@CompositionName='Comment']/ObjectList/MultilingualTextItem/AttributeList/Text" 187 | # ) 188 | # fb_description = fb_description.text if not fb_description is None else None 189 | # if fb_description is None: 190 | # fb_description = "" 191 | # fb = document.add_fb(fb_name, fb_language, fb_description) 192 | 193 | # fb_attributes = child.find("AttributeList") 194 | # fb_objects = child.find("ObjectList") 195 | 196 | # if fb_attributes is None: 197 | # raise ValueError("The FB does not have any attributes.") 198 | 199 | # for fb_attribute in fb_attributes: 200 | # if fb_attribute.tag == "Name" or fb_attribute.tag == "ProgrammingLanguage": 201 | # continue 202 | # if fb_attribute.tag == "AutoNumber": 203 | # fb.auto_number = True if fb_attribute.text == "true" else False 204 | # elif fb_attribute.tag == "HeaderAuthor": 205 | # fb.author = fb_attribute.text if not fb_attribute.text is None else "" 206 | # elif fb_attribute.tag == "HeaderVersion": 207 | # fb.version = fb_attribute.text if not fb_attribute.text is None else "" 208 | # elif fb_attribute.tag == "HeaderFamily": 209 | # fb.family = fb_attribute.text if not fb_attribute.text is None else "" 210 | # elif fb_attribute.tag == "Interface": 211 | # sections = fb_attribute.find( 212 | # "xmlns:Sections", {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 213 | # ) 214 | 215 | # if sections is None: 216 | # raise ValueError("The FB interface does not have any sections.") 217 | 218 | # for section in sections.findall( 219 | # "xmlns:Section", {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 220 | # ): 221 | # members = section.findall( 222 | # "xmlns:Member", {"xmlns": "http://www.siemens.com/automation/Openness/SW/Interface/v3"} 223 | # ) 224 | # for member in members: 225 | # member_name = member.get("Name") 226 | # member_type = member.get("Datatype") 227 | # if member_name is None or member_type is None: 228 | # raise ValueError("The FB interface does not have a name or a type.") 229 | 230 | # if section.get("Name") == "Input": 231 | # fb.add_input(member_name, member_type) 232 | # elif section.get("Name") == "Output": 233 | # fb.add_output(member_name, member_type) 234 | # elif section.get("Name") == "InOut": 235 | # fb.add_in_out(member_name, member_type) 236 | # elif section.get("Name") == "Static": 237 | # fb.add_static(member_name, member_type) 238 | # elif section.get("Name") == "Temp": 239 | # fb.add_temp(member_name, member_type) 240 | # elif section.get("Name") == "Constant": 241 | # fb.add_constant(member_name, member_type) 242 | # else: 243 | # raise ValueError("The FB interface does not have a valid section.") 244 | # elif fb_attribute.tag == "MemoryLayout": 245 | # fb.memory_layout = fb_attribute.text if not fb_attribute.text is None else "" 246 | # elif fb_attribute.tag == "Number": 247 | # fb.number = fb_attribute.text if not fb_attribute.text is None else "" 248 | 249 | # if fb_objects is None: 250 | # raise ValueError("The FB does not have any objects.") 251 | 252 | # fb_compile_units = fb_objects.findall("SW.Blocks.CompileUnit") 253 | 254 | # networks: list[tuple[str, str]] = [] 255 | 256 | # for fb_compile_unit in fb_compile_units: 257 | # is_group = False 258 | # unit_name = fb_compile_unit.find( 259 | # "ObjectList/MultilingualText[@CompositionName='Title']/ObjectList/MultilingualTextItem/AttributeList/Text" 260 | # ) 261 | # unit_name = unit_name.text if not unit_name is None else None 262 | # if unit_name is None: 263 | # raise ValueError("The FB compile unit does not have a name.") 264 | # if unit_name.startswith("***"): 265 | # is_group = True 266 | # unit_name = unit_name.replace("***", "") 267 | 268 | # unit_description = fb_compile_unit.find( 269 | # "ObjectList/MultilingualText[@CompositionName='Comment']/ObjectList/MultilingualTextItem/AttributeList/Text" 270 | # ) 271 | 272 | # unit_description = unit_description.text if not unit_description is None else "" 273 | # unit_description = unit_description if not unit_description is None else "" 274 | 275 | # if is_group: 276 | # fb.add_network_group(unit_name, unit_description) 277 | # continue 278 | 279 | # networks.append((unit_name, unit_description)) 280 | 281 | # print("Networks needs groups to be added") 282 | # print(f"group options are: {'-'.join(fb.network_groups)}") 283 | # for network in networks: 284 | # group = input(f"Group for network {network[0]}: ") 285 | # fb.add_network(network[0], network[1], group) 286 | 287 | # for fb_compile_unit in fb_compile_units: 288 | # unit_name = fb_compile_unit.find( 289 | # "ObjectList/MultilingualText[@CompositionName='Title']/ObjectList/MultilingualTextItem/AttributeList/Text" 290 | # ) 291 | # unit_name = unit_name.text if not unit_name is None else None 292 | # if unit_name is None: 293 | # raise ValueError("The FB compile unit does not have a name.") 294 | # if unit_name.startswith("***"): 295 | # continue 296 | 297 | # network = fb.get_network(unit_name) 298 | 299 | # if network is None: 300 | # raise ValueError("The FB compile unit does not have a network.") 301 | 302 | # attributes = fb_compile_unit.find("AttributeList") 303 | # if attributes is None: 304 | # raise ValueError("The FB compile unit does not have any attributes.") 305 | # network_source = attributes.find("NetworkSource") 306 | # if network_source is None: 307 | # raise ValueError("The FB compile unit does not have a network source.") 308 | 309 | # ns = {"xmlns": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v3"} 310 | # flg_net = network_source.find("xmlns:FlgNet", ns) 311 | 312 | # if flg_net is None: 313 | # continue 314 | 315 | # parts = flg_net.find("xmlns:Parts", ns) 316 | 317 | # if not parts is None: 318 | # parts_list = parts.findall("xmlns:Part", ns) 319 | 320 | # for part in parts_list: 321 | # part_name = part.get("Name") 322 | # part_version = part.get("Version") 323 | 324 | # if part_name is None: 325 | # raise ValueError("The FB network part does not have a name.") 326 | 327 | # network.add_part(part_name, part_version) 328 | 329 | # call_list = parts.findall("xmlns:Call", ns) 330 | 331 | # for call in call_list: 332 | # call_info = call.find("xmlns:CallInfo", ns) 333 | # if call_info is None: 334 | # raise ValueError("The FB network call does not have call info.") 335 | # call_name = call_info.get("Name") 336 | # call_block_type = call_info.get("BlockType") 337 | 338 | # if call_name is None or call_block_type is None: 339 | # raise ValueError("The FB network call does not have a name or a block type.") 340 | 341 | # # network.add_call(call_name, call_block_type) 342 | 343 | # return document 344 | 345 | def save(self, file_name: str) -> str: 346 | """Saves the document.""" 347 | tree = ET.ElementTree(self.build()) 348 | path = os.path.join(self.import_path, f"{file_name}.xml") 349 | if os.path.isfile(path): 350 | os.remove(path) 351 | tree.write(path, encoding="utf-8", xml_declaration=True) 352 | 353 | return path 354 | 355 | def save_no_call(self, file_name: str) -> str: 356 | """Saves the document without the calls.""" 357 | tree = ET.ElementTree(self.build_no_call()) 358 | path = os.path.join(self.import_path, f"{file_name}_no_call.xml") 359 | if os.path.isfile(path): 360 | os.remove(path) 361 | tree.write(path, encoding="utf-8", xml_declaration=True) 362 | 363 | return path 364 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/blocks/ob.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from typing import Optional 3 | import xml.etree.ElementTree as ET 4 | from tia_xml_generator.elements.compile_unit import CompileUnit 5 | from tia_xml_generator.elements.multilingual_text import MultilingualText 6 | from tia_xml_generator.enums import ProgrammingLanguage, SecondaryType 7 | from tia_xml_generator.elements.attribute_list import AttributeList 8 | 9 | from tia_xml_generator.elements.basis import XMLBase 10 | from tia_xml_generator.elements.blocks import Block 11 | from tia_xml_generator.elements.interface import InterfaceOB 12 | from tia_xml_generator.elements.member import Member 13 | from tia_xml_generator.elements.object_list import ObjectList 14 | 15 | 16 | class AttributeListOB(AttributeList): 17 | def __init__(self): 18 | super().__init__() 19 | self.__name = ET.Element("Name") 20 | self.__auto_number = ET.Element("AutoNumber") 21 | self.__author = ET.Element("HeaderAuthor") 22 | self.__family = ET.Element("HeaderFamily") 23 | self.__header_name = ET.Element("HeaderName") 24 | self.__version = ET.Element("HeaderVersion") 25 | self.__memory_layout = ET.Element("MemoryLayout") 26 | self.__number = ET.Element("Number") 27 | self.__programming_language = ET.Element("ProgrammingLanguage") 28 | self.__secondary_type = ET.Element("SecondaryType") 29 | # TODO: Add all attributes 30 | self.__assigned_prodiag_fb = ET.Element("AssignedProDiagFB") 31 | self.__supervisions = ET.Element("Supervisions") 32 | self.__is_iec_check_enabled = ET.Element("IsIECCheckEnabled") 33 | self.__is_retain_mem_res_enabled = ET.Element("IsRetainMemResEnabled") 34 | self.__memory_reserve = ET.Element("MemoryReserve") 35 | self.__retain_memory_reserve = ET.Element("RetainMemoryReserve") 36 | self.__parameter_passing = ET.Element("ParameterPassing") 37 | self.__uda_block_properties = ET.Element("UDABlockProperties") 38 | self.__uda_enable_tag_readback = ET.Element("UDAEnableTagReadback") 39 | self.__library_conformance_status = ET.Element("LibraryConformanceStatus") 40 | self.element.extend([self.__name, self.__programming_language, self.__secondary_type]) 41 | self.load_interface() 42 | 43 | def load_interface(self) -> None: 44 | self.interface = InterfaceOB() 45 | self.add(self.interface) 46 | 47 | @property 48 | def name(self) -> str: 49 | if self.__name.text is None: 50 | return "" 51 | return self.__name.text 52 | 53 | @name.setter 54 | def name(self, name: str) -> None: 55 | self.__name.text = name 56 | 57 | @property 58 | def programming_language(self) -> Optional[ProgrammingLanguage]: 59 | if self.__programming_language.text is None: 60 | return None 61 | return ProgrammingLanguage[self.__programming_language.text] 62 | 63 | @programming_language.setter 64 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 65 | self.__programming_language.text = programming_language.name 66 | 67 | @property 68 | def auto_number(self) -> bool: 69 | if self.__auto_number.text is None: 70 | return False 71 | return self.__auto_number.text == "true" 72 | 73 | @auto_number.setter 74 | def auto_number(self, auto_number: bool) -> None: 75 | self.__auto_number.text = "true" if auto_number else "false" 76 | if self.element.find("AutoNumber") is None: 77 | self.element.append(self.__auto_number) 78 | 79 | if auto_number and self.element.find("Number") is not None: 80 | self.element.remove(self.__number) 81 | 82 | if not auto_number and self.number != "": 83 | self.element.append(self.__number) 84 | 85 | if not auto_number and self.number == "": 86 | raise ValueError("Number must be set if auto_number is False") 87 | 88 | @property 89 | def author(self) -> str: 90 | if self.__author.text is None: 91 | return "" 92 | return self.__author.text 93 | 94 | @author.setter 95 | def author(self, author: str) -> None: 96 | self.__author.text = author 97 | if self.element.find("HeaderAuthor") is None: 98 | self.element.append(self.__author) 99 | 100 | @property 101 | def family(self) -> str: 102 | if self.__family.text is None: 103 | return "" 104 | return self.__family.text 105 | 106 | @family.setter 107 | def family(self, family: str) -> None: 108 | self.__family.text = family 109 | if self.element.find("HeaderFamily") is None: 110 | self.element.append(self.__family) 111 | 112 | @property 113 | def header_name(self) -> str: 114 | if self.__header_name.text is None: 115 | return "" 116 | return self.__header_name.text 117 | 118 | @header_name.setter 119 | def header_name(self, header_name: str) -> None: 120 | self.__header_name.text = header_name 121 | if self.element.find("HeaderName") is None: 122 | self.element.append(self.__header_name) 123 | 124 | @property 125 | def version(self) -> str: 126 | if self.__version.text is None: 127 | return "" 128 | return self.__version.text 129 | 130 | @version.setter 131 | def version(self, version: str) -> None: 132 | self.__version.text = version 133 | if self.element.find("HeaderVersion") is None: 134 | self.element.append(self.__version) 135 | 136 | @property 137 | def memory_layout(self) -> str: 138 | if self.__memory_layout.text is None: 139 | return "" 140 | return self.__memory_layout.text 141 | 142 | @memory_layout.setter 143 | def memory_layout(self, memory_layout: str) -> None: 144 | self.__memory_layout.text = memory_layout 145 | if self.element.find("MemoryLayout") is None: 146 | self.element.append(self.__memory_layout) 147 | 148 | @property 149 | def number(self) -> str: 150 | if self.__number.text is None: 151 | return "" 152 | return self.__number.text 153 | 154 | @number.setter 155 | def number(self, number: str) -> None: 156 | self.__number.text = number 157 | if self.element.find("Number") is None and self.auto_number is False: 158 | self.element.append(self.__number) 159 | 160 | @property 161 | def secondary_type(self) -> str: 162 | if self.__secondary_type.text is None: 163 | return "" 164 | return self.__secondary_type.text 165 | 166 | @secondary_type.setter 167 | def secondary_type(self, secondary_type: SecondaryType) -> None: 168 | self.__secondary_type.text = secondary_type.name 169 | if self.element.find("SecondaryType") is None: 170 | self.element.append(self.__secondary_type) 171 | 172 | 173 | class ObjectListOB(ObjectList): 174 | 175 | network_groups: dict[str, int] 176 | 177 | networks: dict[str, dict[int, CompileUnit]] 178 | 179 | def __init__(self, name: str, description: str): 180 | self.network_groups = {} 181 | self.networks = {} 182 | super().__init__() 183 | self.title = MultilingualText("Title") 184 | self.comment = MultilingualText("Comment") 185 | 186 | self.add(self.title) 187 | self.add(self.comment) 188 | 189 | self.title.add_text(name) 190 | self.comment.add_text(description) 191 | 192 | def add_network( 193 | self, name: str, comment: str, group: str, order: int, programming_language: ProgrammingLanguage 194 | ) -> CompileUnit: 195 | if group not in self.network_groups: 196 | raise ValueError(f"Group {group} not found") 197 | if group not in self.networks: 198 | self.networks[group] = {} 199 | 200 | if order in self.networks[group]: 201 | for i in range(max(self.network_groups.values()), order, -1): 202 | self.networks[group][i] = self.networks[group][i - 1] 203 | 204 | network = CompileUnit(name, programming_language, comment) 205 | self.networks[group][order] = network 206 | 207 | return network 208 | 209 | def add_network_group(self, name: str, comment: str, order: int, programming_language: ProgrammingLanguage) -> None: 210 | if name in self.network_groups: 211 | raise ValueError(f"Group {name} already exists") 212 | if order in self.network_groups.values(): 213 | for i in range(max(self.network_groups.values()), order, -1): 214 | for key, value in self.network_groups.items(): 215 | if value == i: 216 | self.network_groups[key] = i + 1 217 | self.network_groups[name] = order 218 | 219 | self.add_network(f"***{name}***", comment, name, 0, programming_language) 220 | 221 | def build(self) -> ET.Element: 222 | self_ = deepcopy(self) 223 | for group in dict(sorted(self_.network_groups.items(), key=lambda x: x[1])): 224 | for network in dict(sorted(self_.networks[group].items(), key=lambda x: x[0])).values(): 225 | self_.element.append(network.build()) 226 | self_.element.extend([self_.title.build(), self_.comment.build()]) 227 | 228 | return self_.element 229 | 230 | def build_no_call(self) -> ET.Element: 231 | self_ = deepcopy(self) 232 | for group in dict(sorted(self_.network_groups.items(), key=lambda x: x[1])): 233 | for network in dict(sorted(self_.networks[group].items(), key=lambda x: x[0])).values(): 234 | self_.element.append(network.build_no_call()) 235 | self_.element.extend([self_.title.build_no_call(), self_.comment.build_no_call()]) 236 | 237 | return self_.element 238 | 239 | 240 | class OB(XMLBase, Block): 241 | element_name = "SW.Blocks.OB" 242 | attribute_list: AttributeListOB 243 | 244 | def __init__( 245 | self, name: str, programming_language: ProgrammingLanguage, description: str, secondary_type: SecondaryType 246 | ): 247 | super().__init__() 248 | self.load_attribute_list() 249 | self.id = self.global_id.next() 250 | self.description = description 251 | self.name = name 252 | self.programming_language = programming_language 253 | self.secondary_type = secondary_type 254 | self.element = ET.Element(self.element_name, {"ID": self.id}) 255 | self.load_object_list() 256 | 257 | def load_attribute_list(self) -> None: 258 | self.attribute_list = AttributeListOB() 259 | self.add(self.attribute_list) 260 | 261 | def load_object_list(self) -> None: 262 | self.object_list = ObjectListOB(self.name, self.description) 263 | self.add(self.object_list) 264 | 265 | def add_temp(self, name: str, data_type: str) -> Member: 266 | return self.attribute_list.interface.sections.temp.add_member(name, data_type) 267 | 268 | def add_constant(self, name: str, data_type: str) -> Member: 269 | return self.attribute_list.interface.sections.constant.add_member(name, data_type) 270 | 271 | def get_input(self, name: str) -> Optional[Member]: 272 | return self.attribute_list.interface.sections.input.get_member(name) 273 | 274 | def get_temp(self, name: str) -> Optional[Member]: 275 | return self.attribute_list.interface.sections.temp.get_member(name) 276 | 277 | def get_constant(self, name: str) -> Optional[Member]: 278 | return self.attribute_list.interface.sections.constant.get_member(name) 279 | 280 | def add_network( 281 | self, 282 | name: str, 283 | comment: str, 284 | group: str, 285 | order: Optional[int] = None, 286 | programming_language: Optional[ProgrammingLanguage] = None, 287 | ) -> CompileUnit: 288 | programming_language = programming_language if programming_language is not None else self.programming_language 289 | if programming_language is None: 290 | raise ValueError("Programming language is not defined") 291 | if order is None: 292 | order = len(self.object_list.networks[group]) 293 | return self.object_list.add_network(name, comment, group, order, programming_language) 294 | 295 | def add_network_group( 296 | self, 297 | name: str, 298 | comment: str, 299 | order: Optional[int] = None, 300 | programming_language: Optional[ProgrammingLanguage] = None, 301 | ) -> None: 302 | programming_language = programming_language if programming_language is not None else self.programming_language 303 | if programming_language is None: 304 | raise ValueError("Programming language is not defined") 305 | if order is None: 306 | order = len(self.object_list.network_groups) 307 | self.object_list.add_network_group(name, comment, order, programming_language) 308 | 309 | def get_network(self, name: str) -> CompileUnit: 310 | for group in self.object_list.networks.values(): 311 | for network in group.values(): 312 | if network.name == name: 313 | return network 314 | raise ValueError(f"Network {name} not found") 315 | 316 | def get_networks_in_group(self, group: str) -> list[CompileUnit]: 317 | if group not in self.object_list.network_groups: 318 | raise ValueError(f"Network group {group} not found") 319 | return list(self.object_list.networks[group].values()) 320 | 321 | @property 322 | def network_groups(self) -> list[str]: 323 | return list(self.object_list.network_groups.keys()) 324 | 325 | @network_groups.setter 326 | def network_groups(self, network_groups: list[str]) -> None: 327 | raise AttributeError("Network groups cannot be set") 328 | 329 | @property 330 | def author(self) -> str: 331 | return self.attribute_list.author 332 | 333 | @author.setter 334 | def author(self, author: str) -> None: 335 | self.attribute_list.author = author 336 | 337 | @property 338 | def family(self) -> str: 339 | return self.attribute_list.family 340 | 341 | @family.setter 342 | def family(self, family: str) -> None: 343 | self.attribute_list.family = family 344 | 345 | @property 346 | def header_name(self) -> str: 347 | return self.attribute_list.header_name 348 | 349 | @header_name.setter 350 | def header_name(self, header_name: str) -> None: 351 | self.attribute_list.header_name = header_name 352 | 353 | @property 354 | def version(self) -> str: 355 | return self.attribute_list.version 356 | 357 | @version.setter 358 | def version(self, version: str) -> None: 359 | self.attribute_list.version = version 360 | 361 | @property 362 | def memory_layout(self) -> str: 363 | return self.attribute_list.memory_layout 364 | 365 | @memory_layout.setter 366 | def memory_layout(self, memory_layout: str) -> None: 367 | self.attribute_list.memory_layout = memory_layout 368 | 369 | @property 370 | def auto_number(self) -> bool: 371 | return self.attribute_list.auto_number 372 | 373 | @auto_number.setter 374 | def auto_number(self, auto_number: bool) -> None: 375 | self.attribute_list.auto_number = auto_number 376 | 377 | @property 378 | def number(self) -> str: 379 | return self.attribute_list.number 380 | 381 | @number.setter 382 | def number(self, number: str) -> None: 383 | self.attribute_list.number = number 384 | 385 | @property 386 | def programming_language(self) -> Optional[ProgrammingLanguage]: 387 | return self.attribute_list.programming_language 388 | 389 | @programming_language.setter 390 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 391 | self.attribute_list.programming_language = programming_language 392 | 393 | @property 394 | def secondary_type(self) -> str: 395 | return self.attribute_list.secondary_type 396 | 397 | @secondary_type.setter 398 | def secondary_type(self, secondary_type: SecondaryType) -> None: 399 | self.attribute_list.secondary_type = secondary_type 400 | 401 | @property 402 | def name(self) -> str: 403 | return self.attribute_list.name 404 | 405 | @name.setter 406 | def name(self, name: str) -> None: 407 | self.attribute_list.name = name 408 | 409 | @property 410 | def inputs(self) -> list[Member]: 411 | return self.attribute_list.interface.sections.input.members 412 | 413 | @inputs.setter 414 | def inputs(self, inputs: list[Member]) -> None: 415 | raise AttributeError("Inputs cannot be set") 416 | 417 | @property 418 | def temps(self) -> list[Member]: 419 | return self.attribute_list.interface.sections.temp.members 420 | 421 | @temps.setter 422 | def temps(self, temps: list[Member]) -> None: 423 | raise AttributeError("Temps cannot be set") 424 | 425 | @property 426 | def constants(self) -> list[Member]: 427 | return self.attribute_list.interface.sections.constant.members 428 | 429 | @constants.setter 430 | def constants(self, constants: list[Member]) -> None: 431 | raise AttributeError("Constants cannot be set") 432 | 433 | @property 434 | def networks(self) -> list[CompileUnit]: 435 | networks: list[CompileUnit] = [] 436 | for group in self.object_list.networks.values(): 437 | networks.extend(group.values()) 438 | return networks 439 | 440 | @networks.setter 441 | def networks(self, networks: list[CompileUnit]) -> None: 442 | raise AttributeError("Networks cannot be set") 443 | -------------------------------------------------------------------------------- /tia_xml_generator/elements/blocks/fb.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from typing import Optional 3 | import xml.etree.ElementTree as ET 4 | from tia_xml_generator.elements.compile_unit import CompileUnit 5 | from tia_xml_generator.elements.multilingual_text import MultilingualText 6 | from tia_xml_generator.enums import ProgrammingLanguage 7 | from tia_xml_generator.elements.attribute_list import AttributeList 8 | 9 | from tia_xml_generator.elements.basis import XMLBase 10 | from tia_xml_generator.elements.blocks import Block 11 | from tia_xml_generator.elements.interface import Interface 12 | from tia_xml_generator.elements.member import Member 13 | from tia_xml_generator.elements.object_list import ObjectList 14 | 15 | 16 | class AttributeListFB(AttributeList): 17 | def __init__(self): 18 | super().__init__() 19 | self.__name = ET.Element("Name") 20 | self.__auto_number = ET.Element("AutoNumber") 21 | self.__author = ET.Element("HeaderAuthor") 22 | self.__family = ET.Element("HeaderFamily") 23 | self.__header_name = ET.Element("HeaderName") 24 | self.__version = ET.Element("HeaderVersion") 25 | self.__memory_layout = ET.Element("MemoryLayout") 26 | self.__number = ET.Element("Number") 27 | self.__programming_language = ET.Element("ProgrammingLanguage") 28 | # TODO: Add all attributes 29 | self.__supervisions = ET.Element("Supervisions") 30 | self.__is_iec_check_enabled = ET.Element("IsIECCheckEnabled") 31 | self.__is_retain_mem_res_enabled = ET.Element("IsRetainMemResEnabled") 32 | self.__memory_reserve = ET.Element("MemoryReserve") 33 | self.__retain_memory_reserve = ET.Element("RetainMemoryReserve") 34 | self.__parameter_passing = ET.Element("ParameterPassing") 35 | self.__uda_block_properties = ET.Element("UDABlockProperties") 36 | self.__uda_enable_tag_readback = ET.Element("UDAEnableTagReadback") 37 | self.__library_conformance_status = ET.Element("LibraryConformanceStatus") 38 | self.element.extend([self.__name, self.__programming_language]) 39 | self.auto_number = True 40 | self.load_interface() 41 | 42 | def load_interface(self) -> None: 43 | self.interface = Interface() 44 | self.add(self.interface) 45 | 46 | @property 47 | def name(self) -> str: 48 | if self.__name.text is None: 49 | return "" 50 | return self.__name.text 51 | 52 | @name.setter 53 | def name(self, name: str) -> None: 54 | self.__name.text = name 55 | 56 | @property 57 | def programming_language(self) -> Optional[ProgrammingLanguage]: 58 | if self.__programming_language.text is None: 59 | return None 60 | return ProgrammingLanguage[self.__programming_language.text] 61 | 62 | @programming_language.setter 63 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 64 | self.__programming_language.text = programming_language.name 65 | 66 | @property 67 | def auto_number(self) -> bool: 68 | if self.__auto_number.text is None: 69 | return False 70 | return self.__auto_number.text == "true" 71 | 72 | @auto_number.setter 73 | def auto_number(self, auto_number: bool) -> None: 74 | self.__auto_number.text = "true" if auto_number else "false" 75 | if self.element.find("AutoNumber") is None: 76 | self.element.append(self.__auto_number) 77 | 78 | if auto_number and self.element.find("Number") is not None: 79 | self.element.remove(self.__number) 80 | 81 | if not auto_number and self.number != "": 82 | self.element.append(self.__number) 83 | 84 | if not auto_number and self.number == "": 85 | raise ValueError("Number must be set if auto_number is False") 86 | 87 | @property 88 | def author(self) -> str: 89 | if self.__author.text is None: 90 | return "" 91 | return self.__author.text 92 | 93 | @author.setter 94 | def author(self, author: str) -> None: 95 | self.__author.text = author 96 | if self.element.find("HeaderAuthor") is None: 97 | self.element.append(self.__author) 98 | 99 | @property 100 | def family(self) -> str: 101 | if self.__family.text is None: 102 | return "" 103 | return self.__family.text 104 | 105 | @family.setter 106 | def family(self, family: str) -> None: 107 | self.__family.text = family 108 | if self.element.find("HeaderFamily") is None: 109 | self.element.append(self.__family) 110 | 111 | @property 112 | def header_name(self) -> str: 113 | if self.__header_name.text is None: 114 | return "" 115 | return self.__header_name.text 116 | 117 | @header_name.setter 118 | def header_name(self, header_name: str) -> None: 119 | self.__header_name.text = header_name 120 | if self.element.find("HeaderName") is None: 121 | self.element.append(self.__header_name) 122 | 123 | @property 124 | def version(self) -> str: 125 | if self.__version.text is None: 126 | return "" 127 | return self.__version.text 128 | 129 | @version.setter 130 | def version(self, version: str) -> None: 131 | self.__version.text = version 132 | if self.element.find("HeaderVersion") is None: 133 | self.element.append(self.__version) 134 | 135 | @property 136 | def memory_layout(self) -> str: 137 | if self.__memory_layout.text is None: 138 | return "" 139 | return self.__memory_layout.text 140 | 141 | @memory_layout.setter 142 | def memory_layout(self, memory_layout: str) -> None: 143 | self.__memory_layout.text = memory_layout 144 | if self.element.find("MemoryLayout") is None: 145 | self.element.append(self.__memory_layout) 146 | 147 | @property 148 | def number(self) -> str: 149 | if self.__number.text is None: 150 | return "" 151 | return self.__number.text 152 | 153 | @number.setter 154 | def number(self, number: str) -> None: 155 | self.__number.text = number 156 | if self.element.find("Number") is None and self.auto_number is False: 157 | self.element.append(self.__number) 158 | 159 | 160 | class ObjectListFB(ObjectList): 161 | 162 | network_groups: dict[str, int] 163 | 164 | networks: dict[str, dict[int, CompileUnit]] 165 | 166 | def __init__(self, name: str, description: str): 167 | self.network_groups = {} 168 | self.networks = {} 169 | super().__init__() 170 | self.title = MultilingualText("Title") 171 | self.comment = MultilingualText("Comment") 172 | 173 | self.add(self.title) 174 | self.add(self.comment) 175 | 176 | self.title.add_text(name) 177 | self.comment.add_text(description) 178 | 179 | def add_network( 180 | self, name: str, comment: str, group: str, order: int, programming_language: ProgrammingLanguage 181 | ) -> CompileUnit: 182 | if group not in self.network_groups: 183 | raise ValueError(f"Group {group} not found") 184 | if group not in self.networks: 185 | self.networks[group] = {} 186 | 187 | if order in self.networks[group]: 188 | for i in range(max(self.network_groups.values()), order, -1): 189 | self.networks[group][i] = self.networks[group][i - 1] 190 | 191 | network = CompileUnit(name, programming_language, comment) 192 | self.networks[group][order] = network 193 | 194 | return network 195 | 196 | def add_network_group(self, name: str, comment: str, order: int, programming_language: ProgrammingLanguage) -> None: 197 | if name in self.network_groups: 198 | raise ValueError(f"Group {name} already exists") 199 | if order in self.network_groups.values(): 200 | for i in range(max(self.network_groups.values()), order, -1): 201 | for key, value in self.network_groups.items(): 202 | if value == i: 203 | self.network_groups[key] = i + 1 204 | self.network_groups[name] = order 205 | 206 | self.add_network(f"***{name}***", comment, name, 0, programming_language) 207 | 208 | def build(self) -> ET.Element: 209 | self_ = deepcopy(self) 210 | for group in dict(sorted(self_.network_groups.items(), key=lambda x: x[1])): 211 | for network in dict(sorted(self_.networks[group].items(), key=lambda x: x[0])).values(): 212 | self_.element.append(network.build()) 213 | self_.element.extend([self_.title.build(), self_.comment.build()]) 214 | 215 | return self_.element 216 | 217 | def build_no_call(self) -> ET.Element: 218 | self_ = deepcopy(self) 219 | for group in dict(sorted(self_.network_groups.items(), key=lambda x: x[1])): 220 | for network in dict(sorted(self_.networks[group].items(), key=lambda x: x[0])).values(): 221 | self_.element.append(network.build_no_call()) 222 | self_.element.extend([self_.title.build_no_call(), self_.comment.build_no_call()]) 223 | 224 | return self_.element 225 | 226 | 227 | class FB(XMLBase, Block): 228 | element_name = "SW.Blocks.FB" 229 | attribute_list: AttributeListFB 230 | 231 | def __init__(self, name: str, programming_language: ProgrammingLanguage, description: str): 232 | super().__init__() 233 | self.load_attribute_list() 234 | self.id = self.global_id.next() 235 | self.description = description 236 | self.name = name 237 | self.programming_language = programming_language 238 | self.element = ET.Element(self.element_name, {"ID": self.id}) 239 | self.load_object_list() 240 | 241 | def load_attribute_list(self) -> None: 242 | self.attribute_list = AttributeListFB() 243 | self.add(self.attribute_list) 244 | 245 | def load_object_list(self) -> None: 246 | self.object_list = ObjectListFB(self.name, self.description) 247 | self.add(self.object_list) 248 | 249 | def add_input(self, name: str, data_type: str) -> Member: 250 | return self.attribute_list.interface.sections.input.add_member(name, data_type) 251 | 252 | def add_output(self, name: str, data_type: str) -> Member: 253 | return self.attribute_list.interface.sections.output.add_member(name, data_type) 254 | 255 | def add_in_out(self, name: str, data_type: str) -> Member: 256 | return self.attribute_list.interface.sections.in_out.add_member(name, data_type) 257 | 258 | def add_temp(self, name: str, data_type: str) -> Member: 259 | return self.attribute_list.interface.sections.temp.add_member(name, data_type) 260 | 261 | def add_constant(self, name: str, data_type: str) -> Member: 262 | return self.attribute_list.interface.sections.constant.add_member(name, data_type) 263 | 264 | def add_static(self, name: str, data_type: str) -> Member: 265 | return self.attribute_list.interface.sections.static.add_member(name, data_type) 266 | 267 | def get_input(self, name: str) -> Optional[Member]: 268 | return self.attribute_list.interface.sections.input.get_member(name) 269 | 270 | def get_output(self, name: str) -> Optional[Member]: 271 | return self.attribute_list.interface.sections.output.get_member(name) 272 | 273 | def get_in_out(self, name: str) -> Optional[Member]: 274 | return self.attribute_list.interface.sections.in_out.get_member(name) 275 | 276 | def get_temp(self, name: str) -> Optional[Member]: 277 | return self.attribute_list.interface.sections.temp.get_member(name) 278 | 279 | def get_constant(self, name: str) -> Optional[Member]: 280 | return self.attribute_list.interface.sections.constant.get_member(name) 281 | 282 | def get_static(self, name: str) -> Optional[Member]: 283 | return self.attribute_list.interface.sections.static.get_member(name) 284 | 285 | def add_network( 286 | self, 287 | name: str, 288 | comment: str, 289 | group: str, 290 | order: Optional[int] = None, 291 | programming_language: Optional[ProgrammingLanguage] = None, 292 | ) -> CompileUnit: 293 | programming_language = programming_language if programming_language is not None else self.programming_language 294 | if programming_language is None: 295 | raise ValueError("Programming language is not defined") 296 | if order is None: 297 | order = len(self.object_list.networks[group]) 298 | return self.object_list.add_network(name, comment, group, order, programming_language) 299 | 300 | def add_network_group( 301 | self, 302 | name: str, 303 | comment: str, 304 | order: Optional[int] = None, 305 | programming_language: Optional[ProgrammingLanguage] = None, 306 | ) -> None: 307 | programming_language = programming_language if programming_language is not None else self.programming_language 308 | if programming_language is None: 309 | raise ValueError("Programming language is not defined") 310 | if order is None: 311 | order = len(self.object_list.network_groups) 312 | self.object_list.add_network_group(name, comment, order, programming_language) 313 | 314 | def get_network(self, name: str) -> CompileUnit: 315 | for group in self.object_list.networks.values(): 316 | for network in group.values(): 317 | if network.name == name: 318 | return network 319 | raise ValueError(f"Network {name} not found") 320 | 321 | def get_networks_in_group(self, group: str) -> list[CompileUnit]: 322 | if group not in self.object_list.network_groups: 323 | raise ValueError(f"Network group {group} not found") 324 | return list(self.object_list.networks[group].values()) 325 | 326 | @property 327 | def network_groups(self) -> list[str]: 328 | return list(self.object_list.network_groups.keys()) 329 | 330 | @network_groups.setter 331 | def network_groups(self, network_groups: list[str]) -> None: 332 | raise AttributeError("Network groups cannot be set") 333 | 334 | @property 335 | def author(self) -> str: 336 | return self.attribute_list.author 337 | 338 | @author.setter 339 | def author(self, author: str) -> None: 340 | self.attribute_list.author = author 341 | 342 | @property 343 | def family(self) -> str: 344 | return self.attribute_list.family 345 | 346 | @family.setter 347 | def family(self, family: str) -> None: 348 | self.attribute_list.family = family 349 | 350 | @property 351 | def header_name(self) -> str: 352 | return self.attribute_list.header_name 353 | 354 | @header_name.setter 355 | def header_name(self, header_name: str) -> None: 356 | self.attribute_list.header_name = header_name 357 | 358 | @property 359 | def version(self) -> str: 360 | return self.attribute_list.version 361 | 362 | @version.setter 363 | def version(self, version: str) -> None: 364 | self.attribute_list.version = version 365 | 366 | @property 367 | def memory_layout(self) -> str: 368 | return self.attribute_list.memory_layout 369 | 370 | @memory_layout.setter 371 | def memory_layout(self, memory_layout: str) -> None: 372 | self.attribute_list.memory_layout = memory_layout 373 | 374 | @property 375 | def auto_number(self) -> bool: 376 | return self.attribute_list.auto_number 377 | 378 | @auto_number.setter 379 | def auto_number(self, auto_number: bool) -> None: 380 | self.attribute_list.auto_number = auto_number 381 | 382 | @property 383 | def number(self) -> str: 384 | return self.attribute_list.number 385 | 386 | @number.setter 387 | def number(self, number: str) -> None: 388 | self.attribute_list.number = number 389 | 390 | @property 391 | def programming_language(self) -> Optional[ProgrammingLanguage]: 392 | return self.attribute_list.programming_language 393 | 394 | @programming_language.setter 395 | def programming_language(self, programming_language: ProgrammingLanguage) -> None: 396 | self.attribute_list.programming_language = programming_language 397 | 398 | @property 399 | def name(self) -> str: 400 | return self.attribute_list.name 401 | 402 | @name.setter 403 | def name(self, name: str) -> None: 404 | self.attribute_list.name = name 405 | 406 | @property 407 | def inputs(self) -> list[Member]: 408 | return self.attribute_list.interface.sections.input.members 409 | 410 | @inputs.setter 411 | def inputs(self, inputs: list[Member]) -> None: 412 | raise AttributeError("Inputs cannot be set") 413 | 414 | @property 415 | def outputs(self) -> list[Member]: 416 | return self.attribute_list.interface.sections.output.members 417 | 418 | @outputs.setter 419 | def outputs(self, outputs: list[Member]) -> None: 420 | raise AttributeError("Outputs cannot be set") 421 | 422 | @property 423 | def in_outs(self) -> list[Member]: 424 | return self.attribute_list.interface.sections.in_out.members 425 | 426 | @in_outs.setter 427 | def in_outs(self, in_outs: list[Member]) -> None: 428 | raise AttributeError("In outs cannot be set") 429 | 430 | @property 431 | def temps(self) -> list[Member]: 432 | return self.attribute_list.interface.sections.temp.members 433 | 434 | @temps.setter 435 | def temps(self, temps: list[Member]) -> None: 436 | raise AttributeError("Temps cannot be set") 437 | 438 | @property 439 | def constants(self) -> list[Member]: 440 | return self.attribute_list.interface.sections.constant.members 441 | 442 | @constants.setter 443 | def constants(self, constants: list[Member]) -> None: 444 | raise AttributeError("Constants cannot be set") 445 | 446 | @property 447 | def statics(self) -> list[Member]: 448 | return self.attribute_list.interface.sections.static.members 449 | 450 | @statics.setter 451 | def statics(self, statics: list[Member]) -> None: 452 | raise AttributeError("Statics cannot be set") 453 | 454 | @property 455 | def networks(self) -> list[CompileUnit]: 456 | networks: list[CompileUnit] = [] 457 | for group in self.object_list.networks.values(): 458 | networks.extend(group.values()) 459 | return networks 460 | 461 | @networks.setter 462 | def networks(self, networks: list[CompileUnit]) -> None: 463 | raise AttributeError("Networks cannot be set") 464 | --------------------------------------------------------------------------------