MTPPy2.0 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. examples/__init__.py +0 -0
  2. examples/minimal+pid.py +82 -0
  3. examples/minimal.py +32 -0
  4. examples/minimal_recipe.py +117 -0
  5. examples/pea_alarm_base.py +47 -0
  6. examples/pea_minimal.py +258 -0
  7. mtppy/__init__.py +1 -0
  8. mtppy/part1/__init__.py +4 -0
  9. mtppy/part1/empty_folder_test.py +7 -0
  10. mtppy/part3/__init__.py +4 -0
  11. mtppy/part3/data_assembly_set/__init__.py +4 -0
  12. mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
  13. mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
  14. mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
  15. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
  16. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
  17. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
  18. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
  19. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
  20. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
  21. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
  22. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
  23. mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
  24. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
  25. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
  26. mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
  27. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
  28. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
  29. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
  30. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
  31. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
  32. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
  33. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
  34. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
  35. mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
  36. mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
  37. mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
  38. mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
  39. mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
  40. mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
  41. mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
  42. mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
  43. mtppy/part3/data_assembly_set/data_assembly.py +65 -0
  44. mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
  45. mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
  46. mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
  47. mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
  48. mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
  49. mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
  50. mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
  51. mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
  52. mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
  53. mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
  54. mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
  55. mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
  56. mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
  57. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
  58. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
  59. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
  60. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
  61. mtppy/part3/definitions/__init__.py +4 -0
  62. mtppy/part3/definitions/mtp_access.py +15 -0
  63. mtppy/part4/__init__.py +4 -0
  64. mtppy/part4/automation_services/__init__.py +4 -0
  65. mtppy/part4/automation_services/base_functions/__init__.py +4 -0
  66. mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
  67. mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
  68. mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
  69. mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
  70. mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
  71. mtppy/part4/automation_services/helpers/__init__.py +4 -0
  72. mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
  73. mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
  74. mtppy/part4/automation_services/helpers/state_machine.py +167 -0
  75. mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
  76. mtppy/part4/automation_services/helpers/thread_control.py +39 -0
  77. mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
  78. mtppy/part4/automation_services/procedure.py +111 -0
  79. mtppy/part4/automation_services/procedure_test.py +185 -0
  80. mtppy/part4/automation_services/service.py +463 -0
  81. mtppy/part4/automation_services/service_elements/__init__.py +4 -0
  82. mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
  83. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
  84. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
  85. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
  86. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
  87. mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
  88. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
  89. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
  90. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
  91. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
  92. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
  93. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
  94. mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
  95. mtppy/part4/automation_services/service_elements/service_control.py +473 -0
  96. mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
  97. mtppy/part4/automation_services/service_elements/service_element.py +20 -0
  98. mtppy/part4/automation_services/service_test.py +407 -0
  99. mtppy/part4/definitions/__init__.py +4 -0
  100. mtppy/part4/definitions/command_codes.py +58 -0
  101. mtppy/part4/definitions/command_codes_test.py +44 -0
  102. mtppy/part4/definitions/state_codes.py +65 -0
  103. mtppy/part4/definitions/state_codes_test.py +44 -0
  104. mtppy/part4/process_values/__init__.py +4 -0
  105. mtppy/part4/process_values/base_functions/__init__.py +4 -0
  106. mtppy/part4/process_values/base_functions/vqc.py +48 -0
  107. mtppy/part4/process_values/input_elements/__init__.py +4 -0
  108. mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
  109. mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
  110. mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
  111. mtppy/part4/process_values/input_elements/input_element.py +40 -0
  112. mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
  113. mtppy/part5/__init__.py +4 -0
  114. mtppy/part5/communication_object.py +99 -0
  115. mtppy/part5/communication_object_test.py +59 -0
  116. mtppy/part5/definitions/__init__.py +4 -0
  117. mtppy/part5/definitions/iec61131_data_types.py +31 -0
  118. mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
  119. mtppy/part5/definitions/opcua_data_types.py +36 -0
  120. mtppy/part5/definitions/opcua_data_types_test.py +90 -0
  121. mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
  122. mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
  123. mtppy/part5/server_assembly_set/__init__.py +5 -0
  124. mtppy/part6/__init__.py +4 -0
  125. mtppy/part6/alarm_set/__init__.py +4 -0
  126. mtppy/part6/alarm_set/alarm.py +132 -0
  127. mtppy/part6/alarm_set/alarm_test.py +265 -0
  128. mtppy/pea/__init__.py +19 -0
  129. mtppy/pea/data_assembly_factory.py +124 -0
  130. mtppy/pea/data_assembly_factory_test.py +229 -0
  131. mtppy/pea/mtp_generator/__init__.py +4 -0
  132. mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
  133. mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
  134. mtppy/pea/pea.py +139 -0
  135. mtppy/pea/pea_opcua_server/__init__.py +4 -0
  136. mtppy/pea/pea_opcua_server/marshalling.py +60 -0
  137. mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
  138. mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
  139. mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
  140. mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
  141. mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
  142. mtppy/pea/pea_test.py +59 -0
  143. mtppy/pea/rmtp_pea.py +155 -0
  144. mtppy/pea/rmtp_pea_test.py +27 -0
  145. mtppy/pea/service_factory.py +178 -0
  146. mtppy/pea/service_factory_test.py +156 -0
  147. mtppy/pea/user_logic/__init__.py +4 -0
  148. mtppy/pea/user_logic/user_logic_pea.py +72 -0
  149. mtppy/pea/user_logic/user_logic_registry.py +106 -0
  150. mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
  151. mtppy/pea/user_logic/user_logic_service.py +228 -0
  152. mtppy/pea/user_logic/user_logic_service_test.py +289 -0
  153. mtppy/utils/__init__.py +4 -0
  154. mtppy/utils/data_assembly_dict.py +61 -0
  155. mtppy/utils/data_assembly_dict_test.py +107 -0
  156. mtppy/utils/data_item.py +446 -0
  157. mtppy/utils/data_item_com_test.py +82 -0
  158. mtppy/utils/data_item_test.py +325 -0
  159. mtppy/utils/logging.py +23 -0
  160. mtppy/utils/logging_test.py +26 -0
  161. mtppy/utils/mtp_file_handling/__init__.py +4 -0
  162. mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
  163. mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
  164. mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
  165. mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
  166. mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
  167. mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
  168. mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
  169. mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
  170. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
  171. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
  172. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
  173. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
  174. mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
  175. mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
  176. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
  177. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
  178. mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
  179. mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
  180. mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
  181. mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
  182. mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
  183. mtppy/utils/testing.py +31 -0
  184. mtppy2_0-0.1.5.dist-info/METADATA +75 -0
  185. mtppy2_0-0.1.5.dist-info/RECORD +214 -0
  186. mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
  187. mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
  188. mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
  189. mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
  190. tools/__init__.py +0 -0
  191. tools/create_bugreport.py +150 -0
  192. tools/inspect_mtp.py +110 -0
  193. utils/__init__.py +1 -0
  194. utils/file_handling/__init__.py +4 -0
  195. utils/file_handling/cleanup_folder.py +9 -0
  196. utils/file_handling/get_utc_foldername_with_ms.py +15 -0
  197. utils/file_handling/get_work_dir.py +9 -0
  198. utils/file_handling/is_valid_path_for_json.py +11 -0
  199. utils/file_handling/is_valid_path_to_json.py +17 -0
  200. utils/file_handling/load_json_file.py +18 -0
  201. utils/file_handling/read_file_from_file_archive.py +43 -0
  202. utils/file_handling/read_file_string.py +10 -0
  203. utils/file_handling/validate_json_data.py +28 -0
  204. utils/file_handling/write_json_file.py +23 -0
  205. utils/get_uuid.py +18 -0
  206. utils/get_uuid_test.py +17 -0
  207. utils/logging.py +100 -0
  208. utils/logging_test.py +67 -0
  209. utils/opcua/check_node.py +79 -0
  210. utils/resolve_unit/resolve_unit.py +11 -0
  211. utils/resolve_unit/unit_lib.py +2101 -0
  212. utils/resolve_unit/unit_lib_test.py +36 -0
  213. utils/type_conversion.py +236 -0
  214. utils/type_conversion_test.py +114 -0
@@ -0,0 +1,82 @@
1
+ """Tests for IEC-61131 data types."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import pytest
9
+
10
+ from enum import Enum
11
+
12
+ from mtppy.part5.definitions.iec61131_data_types import IEC61131DataTypes
13
+
14
+
15
+ def test_iec61131_data_types_is_enum() -> None:
16
+ assert issubclass(IEC61131DataTypes, Enum)
17
+
18
+
19
+ def test_iec61131_data_types_contains_all_supported_types() -> None:
20
+ expected_names = [
21
+ "BOOL",
22
+ "BYTE",
23
+ "WORD",
24
+ "DWORD",
25
+ "LWORD",
26
+ "SINT",
27
+ "INT",
28
+ "DINT",
29
+ "LINT",
30
+ "USINT",
31
+ "UINT",
32
+ "UDINT",
33
+ "ULINT",
34
+ "REAL",
35
+ "LREAL",
36
+ "STRING",
37
+ "WSTRING",
38
+ "DATE",
39
+ "TIME_OF_DAY",
40
+ "DATE_AND_TIME",
41
+ ]
42
+
43
+ assert [member.name for member in IEC61131DataTypes] == expected_names
44
+
45
+
46
+ @pytest.mark.parametrize(
47
+ ("member", "expected_value"),
48
+ [
49
+ (member_name, (member_name,))
50
+ for member_name in (
51
+ "BOOL",
52
+ "BYTE",
53
+ "WORD",
54
+ "DWORD",
55
+ "LWORD",
56
+ "SINT",
57
+ "INT",
58
+ "DINT",
59
+ "LINT",
60
+ "USINT",
61
+ "UINT",
62
+ "UDINT",
63
+ "ULINT",
64
+ "REAL",
65
+ "LREAL",
66
+ "STRING",
67
+ "WSTRING",
68
+ "DATE",
69
+ "TIME_OF_DAY",
70
+ )
71
+ ]
72
+ + [("DATE_AND_TIME", "DATE_AND_TIME")],
73
+ )
74
+ def test_iec61131_data_type_values(
75
+ member: str, expected_value: tuple[str] | str
76
+ ) -> None:
77
+ assert IEC61131DataTypes[member].value == expected_value
78
+
79
+
80
+ def test_iec61131_data_types_can_be_looked_up_by_value() -> None:
81
+ assert IEC61131DataTypes(("BOOL",)) is IEC61131DataTypes.BOOL
82
+ assert IEC61131DataTypes("DATE_AND_TIME") is IEC61131DataTypes.DATE_AND_TIME
@@ -0,0 +1,36 @@
1
+ """Definition of String Constants for OPCUA Data Types."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from enum import Enum
9
+
10
+
11
+ class OpcUaDataTypes(Enum):
12
+ Boolean = ("Boolean",)
13
+ SByte = ("SByte",)
14
+ Byte = ("Byte",)
15
+ Int16 = ("Int16",)
16
+ UInt16 = ("UInt16",)
17
+ Int32 = ("Int32",)
18
+ UInt32 = ("UInt32",)
19
+ Int64 = ("Int64",)
20
+ UInt64 = ("UInt64",)
21
+ Float = ("Float",)
22
+ Double = ("Double",)
23
+ String = ("String",)
24
+ DateTime = ("DateTime",)
25
+ Guid = ("Guid",)
26
+ ByteString = ("ByteString",)
27
+ XmlElement = ("XmlElement",)
28
+ NodeId = ("NodeId",)
29
+ ExpandedNodeId = ("ExpandedNodeId",)
30
+ StatusCode = ("StatusCode",)
31
+ QualifiedName = ("QualifiedName",)
32
+ LocalizedText = ("LocalizedText",)
33
+ ExtensionObject = ("ExtensionObject",)
34
+ DataValue = ("DataValue",)
35
+ Variant = ("Variant",)
36
+ DiagnosticInfo = "DiagnosticInfo"
@@ -0,0 +1,90 @@
1
+ """Tests for OPC UA data types."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ import pytest
9
+
10
+ from enum import Enum
11
+
12
+ from mtppy.part5.definitions.opcua_data_types import OpcUaDataTypes
13
+
14
+
15
+ def test_opcua_data_types_is_enum() -> None:
16
+ assert issubclass(OpcUaDataTypes, Enum)
17
+
18
+
19
+ def test_opcua_data_types_contains_all_supported_types() -> None:
20
+ expected_names = [
21
+ "Boolean",
22
+ "SByte",
23
+ "Byte",
24
+ "Int16",
25
+ "UInt16",
26
+ "Int32",
27
+ "UInt32",
28
+ "Int64",
29
+ "UInt64",
30
+ "Float",
31
+ "Double",
32
+ "String",
33
+ "DateTime",
34
+ "Guid",
35
+ "ByteString",
36
+ "XmlElement",
37
+ "NodeId",
38
+ "ExpandedNodeId",
39
+ "StatusCode",
40
+ "QualifiedName",
41
+ "LocalizedText",
42
+ "ExtensionObject",
43
+ "DataValue",
44
+ "Variant",
45
+ "DiagnosticInfo",
46
+ ]
47
+
48
+ assert [member.name for member in OpcUaDataTypes] == expected_names
49
+
50
+
51
+ @pytest.mark.parametrize(
52
+ ("member", "expected_value"),
53
+ [
54
+ (member_name, (member_name,))
55
+ for member_name in (
56
+ "Boolean",
57
+ "SByte",
58
+ "Byte",
59
+ "Int16",
60
+ "UInt16",
61
+ "Int32",
62
+ "UInt32",
63
+ "Int64",
64
+ "UInt64",
65
+ "Float",
66
+ "Double",
67
+ "String",
68
+ "DateTime",
69
+ "Guid",
70
+ "ByteString",
71
+ "XmlElement",
72
+ "NodeId",
73
+ "ExpandedNodeId",
74
+ "StatusCode",
75
+ "QualifiedName",
76
+ "LocalizedText",
77
+ "ExtensionObject",
78
+ "DataValue",
79
+ "Variant",
80
+ )
81
+ ]
82
+ + [("DiagnosticInfo", "DiagnosticInfo")],
83
+ )
84
+ def test_opcua_data_type_values(member: str, expected_value: tuple[str] | str) -> None:
85
+ assert OpcUaDataTypes[member].value == expected_value
86
+
87
+
88
+ def test_opcua_data_types_can_be_looked_up_by_value() -> None:
89
+ assert OpcUaDataTypes(("Boolean",)) is OpcUaDataTypes.Boolean
90
+ assert OpcUaDataTypes("DiagnosticInfo") is OpcUaDataTypes.DiagnosticInfo
@@ -0,0 +1,177 @@
1
+ """Conversion of MTP supported automation datatypes."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from collections.abc import Callable
9
+ from enum import Enum
10
+
11
+ from opcua.ua import VariantType
12
+
13
+ import utils.type_conversion as tc
14
+
15
+
16
+ class W3cXmlDataTypes(Enum):
17
+ """Enumeration of W3C XML schema data types supported by MTP 2.0.
18
+
19
+ Each member of this enumeration maps a W3C XML schema data type to the
20
+ corresponding Python type, the matching ``opcua.ua.VariantType``
21
+ and an optional conversion function used to cast Python values to the
22
+ target representation when reading or writing MTP data items.
23
+
24
+ Fully Supported Attributes:
25
+ BOOLEAN,
26
+ BYTE, UNSIGNED_BYTE
27
+ SHORT, UNSIGNED_SHORT
28
+ INT, UNSIGNED_INT
29
+ LONG, UNSIGNED_LONG
30
+ FLOAT, DOUBLE
31
+ STRING
32
+
33
+ Partially Supported Attributes:
34
+ DATE, TIME, DATE_TIME
35
+ ANY_URI, TOKEN
36
+ """
37
+
38
+ BOOLEAN = (
39
+ "boolean",
40
+ bool,
41
+ VariantType.Boolean,
42
+ tc.cast_to_bool,
43
+ )
44
+ UNSIGNED_BYTE = (
45
+ "unsignedByte",
46
+ int,
47
+ VariantType.Byte,
48
+ tc.cast_to_ubyte,
49
+ )
50
+ UNSIGNED_SHORT = (
51
+ "unsignedShort",
52
+ int,
53
+ VariantType.UInt16,
54
+ tc.cast_to_ushort,
55
+ )
56
+ UNSIGNED_INT = (
57
+ "unsignedInt",
58
+ int,
59
+ VariantType.UInt32,
60
+ tc.cast_to_uint,
61
+ )
62
+ UNSIGNED_LONG = (
63
+ "unsignedLong",
64
+ int,
65
+ VariantType.Int64,
66
+ tc.cast_to_ulong,
67
+ )
68
+ BYTE = (
69
+ "byte",
70
+ int,
71
+ VariantType.SByte,
72
+ tc.cast_to_byte,
73
+ )
74
+ SHORT = (
75
+ "short",
76
+ int,
77
+ VariantType.Int16,
78
+ tc.cast_to_short,
79
+ )
80
+ INT = (
81
+ "int",
82
+ int,
83
+ VariantType.Int32,
84
+ tc.cast_to_int,
85
+ )
86
+ LONG = (
87
+ "long",
88
+ int,
89
+ VariantType.Int64,
90
+ tc.cast_to_long,
91
+ )
92
+ FLOAT = (
93
+ "float",
94
+ float,
95
+ VariantType.Float,
96
+ float,
97
+ )
98
+ DOUBLE = (
99
+ "double",
100
+ float,
101
+ VariantType.Double,
102
+ float,
103
+ )
104
+ STRING = (
105
+ "string",
106
+ str,
107
+ VariantType.String,
108
+ tc.cast_to_string,
109
+ )
110
+ DATE = (
111
+ "date",
112
+ None,
113
+ VariantType.DateTime,
114
+ None,
115
+ )
116
+ TIME = (
117
+ "time",
118
+ None,
119
+ VariantType.DateTime,
120
+ None,
121
+ )
122
+ DATE_TIME = (
123
+ "dateTime",
124
+ None,
125
+ VariantType.DateTime,
126
+ None,
127
+ )
128
+ ANY_URI = (
129
+ "anyURI",
130
+ None,
131
+ None,
132
+ None,
133
+ )
134
+ TOKEN = (
135
+ "token",
136
+ None,
137
+ None,
138
+ None,
139
+ )
140
+
141
+ def __init__(
142
+ self,
143
+ string_value: str,
144
+ python_type: type | None,
145
+ opcua_variant: VariantType | None,
146
+ converse_function: Callable[[tc.ANY_TYPE], tc.ANY_TYPE] | None,
147
+ ) -> None:
148
+ """Initialize a W3cXmlDataTypes enum member.
149
+
150
+ Args:
151
+ string_value: The W3C XML schema name of the data type
152
+ (e.g. "boolean", "unsignedInt", a.s.o).
153
+ python_type: The corresponding Python type (e.g. `bool`,
154
+ `int`, `float`, and `str`). May be `None`
155
+ for XML types without a direct Python equivalent
156
+ (e.g. `anyURI`, `token`).
157
+ opcua_variant: The corresponding
158
+ `opcua.ua.VariantType` value. May be ``None`` for XML
159
+ types without a direct OPC UA mapping.
160
+ converse_function: A callable used to cast or convert a Python
161
+ value to the data type's expected Python representation.
162
+ May be ``None`` for types that do not require an explicit
163
+ conversion step.
164
+ """
165
+ self.string_value = string_value
166
+ self.python_type = python_type
167
+ self.opcua_variant = opcua_variant
168
+ self.converse = converse_function
169
+
170
+ def __str__(self) -> str:
171
+ """Return the W3C XML schema name of the data type as string.
172
+
173
+ Returns:
174
+ The XML schema name of the data type as stored in
175
+ ``string_value`` (e.g. ``"boolean"``, ``"unsignedInt"``).
176
+ """
177
+ return self.string_value
@@ -0,0 +1,143 @@
1
+ """Tests for W3C XML data types."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from enum import Enum
9
+
10
+ import pytest
11
+ from opcua.ua import VariantType
12
+
13
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
14
+ import utils.type_conversion as tc
15
+
16
+
17
+ def test_w3c_xml_data_types_is_enum() -> None:
18
+ assert issubclass(W3cXmlDataTypes, Enum)
19
+
20
+
21
+ def test_w3c_xml_data_types_contains_all_supported_types() -> None:
22
+ expected_names = [
23
+ "BOOLEAN",
24
+ "UNSIGNED_BYTE",
25
+ "UNSIGNED_SHORT",
26
+ "UNSIGNED_INT",
27
+ "UNSIGNED_LONG",
28
+ "BYTE",
29
+ "SHORT",
30
+ "INT",
31
+ "LONG",
32
+ "FLOAT",
33
+ "DOUBLE",
34
+ "STRING",
35
+ "DATE",
36
+ "TIME",
37
+ "DATE_TIME",
38
+ "ANY_URI",
39
+ "TOKEN",
40
+ ]
41
+
42
+ assert [member.name for member in W3cXmlDataTypes] == expected_names
43
+
44
+
45
+ @pytest.mark.parametrize(
46
+ ("member", "string_value", "python_type", "opcua_variant"),
47
+ [
48
+ (W3cXmlDataTypes.BOOLEAN, "boolean", bool, VariantType.Boolean),
49
+ (
50
+ W3cXmlDataTypes.UNSIGNED_BYTE,
51
+ "unsignedByte",
52
+ int,
53
+ VariantType.Byte,
54
+ ),
55
+ (
56
+ W3cXmlDataTypes.UNSIGNED_SHORT,
57
+ "unsignedShort",
58
+ int,
59
+ VariantType.UInt16,
60
+ ),
61
+ (
62
+ W3cXmlDataTypes.UNSIGNED_INT,
63
+ "unsignedInt",
64
+ int,
65
+ VariantType.UInt32,
66
+ ),
67
+ (
68
+ W3cXmlDataTypes.UNSIGNED_LONG,
69
+ "unsignedLong",
70
+ int,
71
+ VariantType.Int64,
72
+ ),
73
+ (W3cXmlDataTypes.BYTE, "byte", int, VariantType.SByte),
74
+ (W3cXmlDataTypes.SHORT, "short", int, VariantType.Int16),
75
+ (W3cXmlDataTypes.INT, "int", int, VariantType.Int32),
76
+ (W3cXmlDataTypes.LONG, "long", int, VariantType.Int64),
77
+ (W3cXmlDataTypes.FLOAT, "float", float, VariantType.Float),
78
+ (W3cXmlDataTypes.DOUBLE, "double", float, VariantType.Double),
79
+ (W3cXmlDataTypes.STRING, "string", str, VariantType.String),
80
+ (W3cXmlDataTypes.DATE, "date", None, VariantType.DateTime),
81
+ (W3cXmlDataTypes.TIME, "time", None, VariantType.DateTime),
82
+ (W3cXmlDataTypes.DATE_TIME, "dateTime", None, VariantType.DateTime),
83
+ (W3cXmlDataTypes.ANY_URI, "anyURI", None, None),
84
+ (W3cXmlDataTypes.TOKEN, "token", None, None),
85
+ ],
86
+ )
87
+ def test_w3c_xml_data_type_metadata(
88
+ member: W3cXmlDataTypes,
89
+ string_value: str,
90
+ python_type: type | None,
91
+ opcua_variant: VariantType | None,
92
+ ) -> None:
93
+ assert member.string_value == string_value
94
+ assert member.python_type is python_type
95
+ assert member.opcua_variant is opcua_variant
96
+
97
+
98
+ def test_w3c_xml_data_types_string_representation() -> None:
99
+ assert str(W3cXmlDataTypes.BOOLEAN) == "boolean"
100
+ assert str(W3cXmlDataTypes.DATE_TIME) == "dateTime"
101
+ assert str(W3cXmlDataTypes.ANY_URI) == "anyURI"
102
+
103
+
104
+ @pytest.mark.parametrize(
105
+ ("member", "value", "expected_value"),
106
+ [
107
+ (W3cXmlDataTypes.BOOLEAN, 1, True),
108
+ (W3cXmlDataTypes.UNSIGNED_BYTE, 256, 255),
109
+ (W3cXmlDataTypes.UNSIGNED_SHORT, 65536, 65535),
110
+ (W3cXmlDataTypes.UNSIGNED_INT, 2**32, 2**32 - 1),
111
+ (W3cXmlDataTypes.UNSIGNED_LONG, 2**64, 2**64 - 1),
112
+ (W3cXmlDataTypes.BYTE, 128, 127),
113
+ (W3cXmlDataTypes.SHORT, 2**15, 2**15 - 1),
114
+ (W3cXmlDataTypes.INT, 2**31, 2**31 - 1),
115
+ (W3cXmlDataTypes.LONG, 2**63, 2**63 - 1),
116
+ (W3cXmlDataTypes.FLOAT, "1.5", 1.5),
117
+ (W3cXmlDataTypes.DOUBLE, "2.5", 2.5),
118
+ (W3cXmlDataTypes.STRING, 123, "123"),
119
+ ],
120
+ )
121
+ def test_w3c_xml_data_type_conversions(
122
+ member: W3cXmlDataTypes, value: object, expected_value: object
123
+ ) -> None:
124
+ assert member.converse is not None
125
+ assert member.converse(value) == expected_value
126
+
127
+
128
+ def test_w3c_xml_data_types_without_conversion_are_unmapped() -> None:
129
+ for member in (
130
+ W3cXmlDataTypes.DATE,
131
+ W3cXmlDataTypes.TIME,
132
+ W3cXmlDataTypes.DATE_TIME,
133
+ W3cXmlDataTypes.ANY_URI,
134
+ W3cXmlDataTypes.TOKEN,
135
+ ):
136
+ assert member.converse is None
137
+
138
+
139
+ def test_w3c_xml_data_types_can_be_looked_up_by_value() -> None:
140
+ assert (
141
+ W3cXmlDataTypes(("boolean", bool, VariantType.Boolean, tc.cast_to_bool))
142
+ is W3cXmlDataTypes.BOOLEAN
143
+ )
@@ -0,0 +1,5 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
5
+
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -0,0 +1,4 @@
1
+ __copyright__ = (
2
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
3
+ )
4
+ __license__ = "MIT"
@@ -0,0 +1,132 @@
1
+ """Static AlarmSet.Base alarm descriptions."""
2
+
3
+ __copyright__ = (
4
+ "Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
5
+ )
6
+ __license__ = "MIT"
7
+
8
+ from dataclasses import dataclass
9
+
10
+ from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
11
+ from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
12
+ from mtppy.utils.data_item import DataItem
13
+
14
+ MIN_SEVERITY = 1
15
+ MAX_SEVERITY = 1000
16
+ _INTEGER_RANGES = {
17
+ W3cXmlDataTypes.BYTE: (-(2**7), (2**7) - 1),
18
+ W3cXmlDataTypes.UNSIGNED_BYTE: (0, (2**8) - 1),
19
+ W3cXmlDataTypes.SHORT: (-(2**15), (2**15) - 1),
20
+ W3cXmlDataTypes.UNSIGNED_SHORT: (0, (2**16) - 1),
21
+ W3cXmlDataTypes.INT: (-(2**31), (2**31) - 1),
22
+ W3cXmlDataTypes.UNSIGNED_INT: (0, (2**32) - 1),
23
+ W3cXmlDataTypes.LONG: (-(2**63), (2**63) - 1),
24
+ W3cXmlDataTypes.UNSIGNED_LONG: (0, (2**64) - 1),
25
+ }
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class Alarm:
30
+ """Describe a POL-evaluated AlarmSet.Base alarm.
31
+
32
+ The alarm links a value of an existing DataItem to static alarm metadata in
33
+ the generated MTP. It does not observe the DataItem or maintain alarm state
34
+ inside the PEA.
35
+
36
+ Args:
37
+ tag_name: Unique alarm name within the PEA.
38
+ data_assembly: DataAssembly containing the trigger DataItem.
39
+ trigger: Existing DataItem used as the alarm trigger.
40
+ trigger_value: Value for which the POL considers the alarm active.
41
+ message: Operator-facing alarm message.
42
+ ack_required: Whether the POL should require acknowledgement.
43
+ severity: Alarm severity in the inclusive range from 1 to 1000.
44
+
45
+ Raises:
46
+ TypeError: If an argument has an unsupported type.
47
+ ValueError: If text is empty, severity is out of range, or the trigger
48
+ does not belong to the DataAssembly.
49
+ """
50
+
51
+ tag_name: str
52
+ data_assembly: DataAssembly
53
+ trigger: DataItem
54
+ trigger_value: object
55
+ message: str
56
+ ack_required: bool
57
+ severity: int
58
+
59
+ def __post_init__(self) -> None:
60
+ """Validate the static alarm description."""
61
+ self.validate()
62
+
63
+ def validate(self) -> None:
64
+ """Revalidate this alarm and its mutable DataItem reference.
65
+
66
+ Raises:
67
+ TypeError: If a field or trigger type is unsupported.
68
+ ValueError: If a value is outside its permitted range or the trigger
69
+ no longer belongs to the configured DataAssembly.
70
+ """
71
+ self._validate_text(self.tag_name, "tag_name")
72
+ self._validate_text(self.message, "message")
73
+
74
+ if not isinstance(self.data_assembly, DataAssembly):
75
+ msg = "data_assembly must be a DataAssembly"
76
+ raise TypeError(msg)
77
+ if not isinstance(self.trigger, DataItem):
78
+ msg = "trigger must be a DataItem"
79
+ raise TypeError(msg)
80
+ if not any(
81
+ self.data_assembly.get_item(key) is self.trigger
82
+ for key in self.data_assembly
83
+ ):
84
+ msg = "trigger must belong to the configured DataAssembly"
85
+ raise ValueError(msg)
86
+
87
+ trigger_type = self.trigger.w3c_xml_data_type.python_type
88
+ if trigger_type not in (bool, int, str):
89
+ type_name = self.trigger.w3c_xml_data_type.string_value
90
+ msg = f"Alarm trigger type {type_name} is not supported"
91
+ raise TypeError(msg)
92
+ if type(self.trigger_value) is not trigger_type:
93
+ msg = "TriggerValue must have the exact type of the trigger DataItem"
94
+ raise TypeError(msg)
95
+ integer_range = _INTEGER_RANGES.get(self.trigger.w3c_xml_data_type)
96
+ if integer_range is not None and not (
97
+ integer_range[0] <= self.trigger_value <= integer_range[1]
98
+ ):
99
+ msg = (
100
+ f"TriggerValue is outside the range of "
101
+ f"xs:{self.trigger.w3c_xml_data_type.string_value}"
102
+ )
103
+ raise ValueError(msg)
104
+
105
+ if type(self.ack_required) is not bool:
106
+ msg = "ack_required must be a boolean"
107
+ raise TypeError(msg)
108
+ if type(self.severity) is not int:
109
+ msg = "severity must be an integer and bool is not accepted"
110
+ raise TypeError(msg)
111
+ if not MIN_SEVERITY <= self.severity <= MAX_SEVERITY:
112
+ msg = f"severity must be between {MIN_SEVERITY} and {MAX_SEVERITY}"
113
+ raise ValueError(msg)
114
+
115
+ @staticmethod
116
+ def _validate_text(value: str, field_name: str) -> None:
117
+ """Validate a required text field.
118
+
119
+ Args:
120
+ value: Field value to validate.
121
+ field_name: Field name used in the error message.
122
+
123
+ Raises:
124
+ TypeError: If the value is not a string.
125
+ ValueError: If the value contains no visible characters.
126
+ """
127
+ if not isinstance(value, str):
128
+ msg = f"{field_name} must be a string"
129
+ raise TypeError(msg)
130
+ if not value.strip():
131
+ msg = f"{field_name} must not be empty"
132
+ raise ValueError(msg)