Spec parsing: Add global commands for thermostat (#35607)

* Spec parsing: Add global commands for thermostat

* use in-progress for clusters too

* Fix command check (merge conflict?)

* Restyled by isort

* Fix test that checks the default cluster dir

---------

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/python_testing/TC_DeviceConformance.py b/src/python_testing/TC_DeviceConformance.py
index a6d6f19..86a981c 100644
--- a/src/python_testing/TC_DeviceConformance.py
+++ b/src/python_testing/TC_DeviceConformance.py
@@ -166,7 +166,6 @@
                     if attribute_id not in self.xml_clusters[cluster_id].attributes.keys():
                         # TODO: Consolidate the range checks with IDM-10.1 once that lands
                         if attribute_id <= 0x4FFF:
-                            # manufacturer attribute
                             record_error(location=location, problem='Standard attribute found on device, but not in spec')
                         continue
                     xml_attribute = self.xml_clusters[cluster_id].attributes[attribute_id]
@@ -193,9 +192,7 @@
                         if command_id not in xml_commands_dict:
                             # TODO: Consolidate range checks with IDM-10.1 once that lands
                             if command_id <= 0xFF:
-                                # manufacturer command
-                                continue
-                            record_error(location=location, problem='Standard command found on device, but not in spec')
+                                record_error(location=location, problem='Standard command found on device, but not in spec')
                             continue
                         xml_command = xml_commands_dict[command_id]
                         conformance_decision_with_choice = xml_command.conformance(feature_map, attribute_list, all_command_list)
diff --git a/src/python_testing/TestSpecParsingSupport.py b/src/python_testing/TestSpecParsingSupport.py
index aa2a684..c7f087c 100644
--- a/src/python_testing/TestSpecParsingSupport.py
+++ b/src/python_testing/TestSpecParsingSupport.py
@@ -272,7 +272,8 @@
         asserts.assert_equal(set(in_progress.keys())-set(tot_xml_clusters.keys()),
                              set(), "There are some in_progress clusters that are not included in the TOT spec")
 
-        str_path = str(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'data_model', 'master', 'clusters'))
+        str_path = str(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                       '..', '..', 'data_model', 'in_progress', 'clusters'))
         string_override_check, problems = build_xml_clusters(str_path)
         asserts.assert_equal(string_override_check.keys(), self.spec_xml_clusters.keys(), "Mismatched cluster generation")
 
@@ -462,6 +463,32 @@
         asserts.assert_in(id, clusters.keys(), "Non-provisional cluster not parsed")
         asserts.assert_false(clusters[id].is_provisional, "Non-provisional cluster marked as provisional")
 
+    def test_atomic_thermostat(self):
+        tot_xml_clusters, problems = build_xml_clusters(PrebuiltDataModelDirectory.kMaster)
+        one_three_clusters, problems = build_xml_clusters(PrebuiltDataModelDirectory.k1_3)
+        in_progress, problems = build_xml_clusters(PrebuiltDataModelDirectory.kInProgress)
+
+        asserts.assert_in("Atomic Request", tot_xml_clusters[Clusters.Thermostat.id].command_map,
+                          "Atomic request not found on thermostat command map")
+        request_id = tot_xml_clusters[Clusters.Thermostat.id].command_map["Atomic Request"]
+        asserts.assert_in(request_id, tot_xml_clusters[Clusters.Thermostat.id].accepted_commands.keys(),
+                          "Atomic request not found in thermostat accepted command list")
+
+        asserts.assert_in("Atomic Response", tot_xml_clusters[Clusters.Thermostat.id].command_map,
+                          "Atomic response not found in the thermostat command map")
+        response_id = tot_xml_clusters[Clusters.Thermostat.id].command_map["Atomic Response"]
+        asserts.assert_in(response_id, tot_xml_clusters[Clusters.Thermostat.id].generated_commands.keys(),
+                          "Atomic response not found in thermostat generated command list")
+
+        asserts.assert_not_in(
+            "Atomic Request", one_three_clusters[Clusters.Thermostat.id].command_map, "Atomic request found on thermostat command map for 1.3")
+        asserts.assert_not_in(request_id, one_three_clusters[Clusters.Thermostat.id].accepted_commands.keys(),
+                              "Atomic request found in thermostat accepted command list for 1.3")
+        asserts.assert_not_in(
+            "Atomic Response", one_three_clusters[Clusters.Thermostat.id].command_map, "Atomic response found on thermostat command map for 1.3")
+        asserts.assert_not_in(response_id, one_three_clusters[Clusters.Thermostat.id].generated_commands.keys(),
+                              "Atomic request found in thermostat generated command list for 1.3")
+
 
 if __name__ == "__main__":
     default_matter_test_main()
diff --git a/src/python_testing/spec_parsing_support.py b/src/python_testing/spec_parsing_support.py
index 445e168..5a66a90 100644
--- a/src/python_testing/spec_parsing_support.py
+++ b/src/python_testing/spec_parsing_support.py
@@ -26,6 +26,7 @@
 from typing import Callable, Optional
 
 import chip.clusters as Clusters
+import conformance_support
 from chip.tlv import uint
 from conformance_support import (OPTIONAL_CONFORM, TOP_LEVEL_CONFORMANCE_TAGS, ConformanceDecision, ConformanceException,
                                  ConformanceParseParameters, feature, is_disallowed, mandatory, optional, or_operation,
@@ -528,7 +529,7 @@
         return data_model_directory
 
 
-def build_xml_clusters(data_model_directory: typing.Union[PrebuiltDataModelDirectory, str] = PrebuiltDataModelDirectory.kMaster) -> tuple[dict[uint, XmlCluster], list[ProblemNotice]]:
+def build_xml_clusters(data_model_directory: typing.Union[PrebuiltDataModelDirectory, str] = PrebuiltDataModelDirectory.kInProgress) -> tuple[dict[uint, XmlCluster], list[ProblemNotice]]:
     dir = _get_data_model_directory(data_model_directory, DataModelLevel.kCluster)
 
     clusters: dict[int, XmlCluster] = {}
@@ -611,6 +612,25 @@
             0x05: XmlAttribute(name='SupportedTemperatureLevels', datatype='list', conformance=feature(0x02, 'TL'), read_access=view, write_access=none, write_optional=False),
         }
 
+    # TODO: Need automated parsing for atomic attributes.
+    atomic_request_cmd_id = 0xFE
+    atomic_response_cmd_id = 0xFD
+    atomic_request_name = "Atomic Request"
+    atomic_response_name = "Atomic Response"
+    presets_name = "Presets"
+    schedules_name = "Schedules"
+    if clusters[Clusters.Thermostat.id].revision >= 8:
+        presents_id = clusters[Clusters.Thermostat.id].attribute_map[presets_name]
+        schedules_id = clusters[Clusters.Thermostat.id].attribute_map[schedules_name]
+        conformance = or_operation([conformance_support.attribute(presents_id, presets_name),
+                                   conformance_support.attribute(schedules_id, schedules_name)])
+        clusters[Clusters.Thermostat.id].accepted_commands[atomic_request_cmd_id] = XmlCommand(
+            id=atomic_request_cmd_id, name=atomic_request_name, conformance=conformance)
+        clusters[Clusters.Thermostat.id].generated_commands[atomic_response_cmd_id] = XmlCommand(
+            id=atomic_response_cmd_id, name=atomic_response_name, conformance=conformance)
+        clusters[Clusters.Thermostat.id].command_map[atomic_request_name] = atomic_request_cmd_id
+        clusters[Clusters.Thermostat.id].command_map[atomic_response_name] = atomic_response_cmd_id
+
     check_clusters_for_unknown_commands(clusters, problems)
 
     return clusters, problems