Fan Control Cluster updates for Fall 2023: TE1 (#26296)

* Updated fan-control-server with better support for feature checking, step command handler and checks around the supported attributes in the pre-callback

* Updated fan-control-cluster.xml for TE1 for fall release and regenerated the generated code

* Ensured status was always initialised before returning, added missing break in switch case

* Update optional parameters of Step command and matched spec naming. Improved checking on Auto feature when receiving smart mode.

* Added a stop-gap fan-stub.cpp so that the existing FanControl tests pass for TE1

* removed unused variable

* fixed return after else warning

* reordered files as per restyler
diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
index 95e20cb..b087a5f 100644
--- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
+++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter
@@ -3285,6 +3285,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 server cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -3309,6 +3319,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
@@ -3338,12 +3350,21 @@
   attribute bitmap8 rockSetting = 8;
   readonly attribute bitmap8 windSupport = 9;
   attribute bitmap8 windSetting = 10;
+  attribute AirflowDirectionEnum airflowDirection = 11;
   readonly attribute command_id generatedCommandList[] = 65528;
   readonly attribute command_id acceptedCommandList[] = 65529;
   readonly attribute event_id eventList[] = 65530;
   readonly attribute attrib_id attributeList[] = 65531;
   readonly attribute bitmap32 featureMap = 65532;
   readonly attribute int16u clusterRevision = 65533;
+
+  request struct StepRequest {
+    DirectionEnum direction = 0;
+    optional boolean wrap = 1;
+    optional boolean lowestOff = 2;
+  }
+
+  command Step(StepRequest): DefaultSuccess = 0;
 }
 
 /** An interface for configuring the user interface of a thermostat (which may be remote from the thermostat). */
@@ -5330,6 +5351,7 @@
     ram      attribute rockSetting default = 0x00;
     ram      attribute windSupport default = 0x00;
     ram      attribute windSetting default = 0x00;
+    ram      attribute airflowDirection default = 0;
     ram      attribute featureMap default = 0x0F;
     ram      attribute clusterRevision default = 2;
   }
diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
index c872509..4cea978 100644
--- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
+++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap
@@ -15716,7 +15716,17 @@
           "mfgCode": null,
           "define": "FAN_CONTROL_CLUSTER",
           "side": "client",
-          "enabled": 0
+          "enabled": 0,
+          "commands": [
+            {
+              "name": "Step",
+              "code": 0,
+              "mfgCode": null,
+              "source": "client",
+              "incoming": 1,
+              "outgoing": 0
+            }
+          ]
         },
         {
           "name": "Fan Control",
@@ -15903,6 +15913,22 @@
               "reportableChange": 0
             },
             {
+              "name": "AirflowDirection",
+              "code": 11,
+              "mfgCode": null,
+              "side": "server",
+              "type": "AirflowDirectionEnum",
+              "included": 1,
+              "storageOption": "RAM",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "0",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
               "name": "GeneratedCommandList",
               "code": 65528,
               "mfgCode": null,
diff --git a/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp
new file mode 100644
index 0000000..4f70f80
--- /dev/null
+++ b/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp
@@ -0,0 +1,115 @@
+/*
+ *
+ *    Copyright (c) 2023 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/AttributeAccessInterface.h>
+#include <app/util/attribute-storage.h>
+#include <lib/support/CodeUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::FanControl::Attributes;
+
+namespace {
+
+/*
+ * TODO: This is a stop-gap solution to allow the existing fan control cluster tests to run after changes to
+ * the cluster objects for TE1. This should be removed once #6496 is resolved as it will likely result in a
+ * FanControl delegate added to the SDK.
+ *
+ * FYI... The previous implementation of the FanControl cluster set the speedCurrent/percentCurrent when it received
+ * speedSetting/percentSetting. The new implementation of the FanControl cluster does not do this as this should
+ * really be done by the application.
+ */
+
+class FanAttrAccess : public AttributeAccessInterface
+{
+public:
+    // Register for the FanControl cluster on all endpoints.
+    FanAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), FanControl::Id) {}
+
+    CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
+
+private:
+    CHIP_ERROR ReadPercentCurrent(EndpointId endpoint, AttributeValueEncoder & aEncoder);
+    CHIP_ERROR ReadSpeedCurrent(EndpointId endpoint, AttributeValueEncoder & aEncoder);
+};
+
+CHIP_ERROR FanAttrAccess::ReadPercentCurrent(EndpointId endpoint, AttributeValueEncoder & aEncoder)
+{
+    // Return PercentSetting attribute value for now
+    DataModel::Nullable<uint8_t> percentSetting;
+    PercentSetting::Get(endpoint, percentSetting);
+    uint8_t ret = 0;
+    if (!percentSetting.IsNull())
+    {
+        ret = percentSetting.Value();
+    }
+
+    return aEncoder.Encode(ret);
+}
+
+CHIP_ERROR FanAttrAccess::ReadSpeedCurrent(EndpointId endpoint, AttributeValueEncoder & aEncoder)
+{
+    // Return SpeedCurrent attribute value for now
+    DataModel::Nullable<uint8_t> speedSetting;
+    SpeedSetting::Get(endpoint, speedSetting);
+    uint8_t ret = 0;
+    if (!speedSetting.IsNull())
+    {
+        ret = speedSetting.Value();
+    }
+
+    return aEncoder.Encode(ret);
+}
+
+FanAttrAccess gAttrAccess;
+
+CHIP_ERROR FanAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+    VerifyOrDie(aPath.mClusterId == FanControl::Id);
+
+    switch (aPath.mAttributeId)
+    {
+    case SpeedCurrent::Id:
+        return ReadSpeedCurrent(aPath.mEndpointId, aEncoder);
+    case PercentCurrent::Id:
+        return ReadPercentCurrent(aPath.mEndpointId, aEncoder);
+    default:
+        break;
+    }
+    return CHIP_NO_ERROR;
+}
+} // anonymous namespace
+
+void emberAfFanControlClusterInitCallback(EndpointId endpoint)
+{
+    uint32_t featureMap = 0;
+
+    featureMap |= to_underlying(FanControl::Feature::kMultiSpeed);
+    featureMap |= to_underlying(FanControl::Feature::kMultiSpeed);
+    featureMap |= to_underlying(FanControl::Feature::kAuto);
+
+    FeatureMap::Set(endpoint, featureMap);
+
+    registerAttributeAccessOverride(&gAttrAccess);
+}
diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn
index 1b8ff71..402649c 100644
--- a/examples/all-clusters-app/linux/BUILD.gn
+++ b/examples/all-clusters-app/linux/BUILD.gn
@@ -23,6 +23,7 @@
   sources = [
     "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp",
     "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp",
+    "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp",
     "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp",
     "AllClustersCommandDelegate.cpp",
     "AppOptions.cpp",
diff --git a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
index 1193a78..8281f56 100644
--- a/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
+++ b/examples/all-clusters-minimal-app/all-clusters-common/all-clusters-minimal-app.matter
@@ -2769,6 +2769,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 server cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -2793,6 +2803,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
diff --git a/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter b/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter
index d897406..00a4d90 100644
--- a/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter
+++ b/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter
@@ -1187,6 +1187,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 server cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -1211,6 +1221,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
diff --git a/examples/chef/devices/rootnode_heatingcoolingunit_ncdGai1E5a.matter b/examples/chef/devices/rootnode_heatingcoolingunit_ncdGai1E5a.matter
index dce6707..14a6168 100644
--- a/examples/chef/devices/rootnode_heatingcoolingunit_ncdGai1E5a.matter
+++ b/examples/chef/devices/rootnode_heatingcoolingunit_ncdGai1E5a.matter
@@ -1500,6 +1500,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 server cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -1524,6 +1534,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
diff --git a/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter b/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter
index 17d0226..a5a3b48 100644
--- a/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter
+++ b/examples/chef/devices/rootnode_thermostat_bm3fb8dhYi.matter
@@ -1309,6 +1309,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 client cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -1333,6 +1343,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
@@ -1362,12 +1374,22 @@
   attribute optional bitmap8 rockSetting = 8;
   readonly attribute optional bitmap8 windSupport = 9;
   attribute optional bitmap8 windSetting = 10;
+  attribute optional AirflowDirectionEnum airflowDirection = 11;
   readonly attribute command_id generatedCommandList[] = 65528;
   readonly attribute command_id acceptedCommandList[] = 65529;
   readonly attribute event_id eventList[] = 65530;
   readonly attribute attrib_id attributeList[] = 65531;
   readonly attribute bitmap32 featureMap = 65532;
   readonly attribute int16u clusterRevision = 65533;
+
+  request struct StepRequest {
+    DirectionEnum direction = 0;
+    optional boolean wrap = 1;
+    optional boolean lowestOff = 2;
+  }
+
+  /** The Step command speeds up or slows down the fan, in steps. */
+  command Step(StepRequest): DefaultSuccess = 0;
 }
 
 /** An interface for configuring the user interface of a thermostat (which may be remote from the thermostat). */
diff --git a/src/app/clusters/fan-control-server/fan-control-server.cpp b/src/app/clusters/fan-control-server/fan-control-server.cpp
index 25b593b..c2c9c1c 100644
--- a/src/app/clusters/fan-control-server/fan-control-server.cpp
+++ b/src/app/clusters/fan-control-server/fan-control-server.cpp
@@ -28,7 +28,10 @@
 #include <app-common/zap-generated/ids/Attributes.h>
 #include <app-common/zap-generated/ids/Clusters.h>
 #include <app/AttributeAccessInterface.h>
+#include <app/CommandHandler.h>
+#include <app/ConcreteCommandPath.h>
 #include <app/util/attribute-storage.h>
+#include <app/util/error-mapping.h>
 #include <lib/support/CodeUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 
@@ -57,6 +60,45 @@
     return status;
 }
 
+bool HasFeature(EndpointId endpoint, Feature feature)
+{
+    bool success;
+    uint32_t featureMap;
+    success = (Attributes::FeatureMap::Get(endpoint, &featureMap) == EMBER_ZCL_STATUS_SUCCESS);
+
+    return success ? ((featureMap & to_underlying(feature)) != 0) : false;
+}
+
+inline bool SupportsMultiSpeed(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kMultiSpeed);
+}
+
+inline bool SupportsAuto(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kAuto);
+}
+
+inline bool SupportsRocking(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kRocking);
+}
+
+inline bool SupportsWind(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kWind);
+}
+
+inline bool SupportsStep(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kStep);
+}
+
+inline bool SupportsAirflowDirection(chip::EndpointId endpointId)
+{
+    return HasFeature(endpointId, Feature::kAirflowDirection);
+}
+
 } // anonymous namespace
 
 // =============================================================================
@@ -73,37 +115,75 @@
 
     switch (attributePath.mAttributeId)
     {
-    case SpeedSetting::Id:
-        // Check if the SpeedSetting is null.
-        if (NumericAttributeTraits<uint8_t>::IsNullValue(*value))
+    case FanMode::Id: {
+        if (*value == to_underlying(FanModeType::kOn))
         {
+            FanMode::Set(attributePath.mEndpointId, FanModeType::kHigh);
+            res = Status::WriteIgnored;
+        }
+        else if (*value == to_underlying(FanModeType::kSmart))
+        {
+            FanModeSequenceType fanModeSequence;
+            EmberAfStatus status = FanModeSequence::Get(attributePath.mEndpointId, &fanModeSequence);
+            VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, Status::Failure);
 
-            if (gWriteFromClusterLogic)
+            if (SupportsAuto(attributePath.mEndpointId) &&
+                ((fanModeSequence == FanModeSequenceType::kOffLowHighAuto) ||
+                 (fanModeSequence == FanModeSequenceType::kOffLowMedHighAuto)))
             {
-                res                    = Status::Success;
-                gWriteFromClusterLogic = false;
+                FanMode::Set(attributePath.mEndpointId, FanModeType::kAuto);
             }
             else
             {
-                res = Status::WriteIgnored;
+                FanMode::Set(attributePath.mEndpointId, FanModeType::kHigh);
+            }
+            res = Status::WriteIgnored;
+        }
+        else
+        {
+            res = Status::Success;
+        }
+        break;
+    }
+    case SpeedSetting::Id: {
+        if (SupportsMultiSpeed(attributePath.mEndpointId))
+        {
+            // Check if the SpeedSetting is null.
+            if (NumericAttributeTraits<uint8_t>::IsNullValue(*value))
+            {
+
+                if (gWriteFromClusterLogic)
+                {
+                    res                    = Status::Success;
+                    gWriteFromClusterLogic = false;
+                }
+                else
+                {
+                    res = Status::WriteIgnored;
+                }
+            }
+            else
+            {
+                uint8_t speedMax;
+                EmberAfStatus status = SpeedMax::Get(attributePath.mEndpointId, &speedMax);
+                VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, Status::ConstraintError);
+
+                if (*value <= speedMax)
+                {
+                    res = Status::Success;
+                }
+                else
+                {
+                    res = Status::ConstraintError;
+                }
             }
         }
         else
         {
-            uint8_t speedMax;
-            EmberAfStatus status = SpeedMax::Get(attributePath.mEndpointId, &speedMax);
-            VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, Status::ConstraintError);
-
-            if (*value <= speedMax)
-            {
-                res = Status::Success;
-            }
-            else
-            {
-                res = Status::ConstraintError;
-            }
+            res = Status::UnsupportedAttribute;
         }
         break;
+    }
     case PercentSetting::Id: {
         // Check if the PercentSetting is null.
         if (NumericAttributeTraits<uint8_t>::IsNullValue(*value))
@@ -124,6 +204,59 @@
         }
         break;
     }
+    case RockSetting::Id: {
+        if (SupportsRocking(attributePath.mEndpointId))
+        {
+            uint8_t rockSupport;
+            EmberAfStatus status = RockSupport::Get(attributePath.mEndpointId, &rockSupport);
+            VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, Status::ConstraintError);
+            if ((*value & rockSupport) == *value)
+            {
+                res = Status::Success;
+            }
+            else
+            {
+                res = Status::ConstraintError;
+            }
+        }
+        else
+        {
+            res = Status::UnsupportedAttribute;
+        }
+        break;
+    }
+    case WindSupport::Id: {
+        if (SupportsWind(attributePath.mEndpointId))
+        {
+            uint8_t windSupport;
+            EmberAfStatus status = WindSupport::Get(attributePath.mEndpointId, &windSupport);
+            VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, Status::ConstraintError);
+            if ((*value & windSupport) == *value)
+            {
+                res = Status::Success;
+            }
+            else
+            {
+                res = Status::ConstraintError;
+            }
+        }
+        else
+        {
+            res = Status::UnsupportedAttribute;
+        }
+        break;
+    }
+    case AirflowDirection::Id: {
+        if (SupportsAirflowDirection(attributePath.mEndpointId))
+        {
+            res = Status::Success;
+        }
+        else
+        {
+            res = Status::UnsupportedAttribute;
+        }
+        break;
+    }
     default:
         res = Status::Success;
         break;
@@ -134,17 +267,6 @@
 
 void MatterFanControlClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath)
 {
-    bool multiSpeedSupported = false;
-
-    {
-        uint32_t ourFeatureMap;
-        if (FeatureMap::Get(attributePath.mEndpointId, &ourFeatureMap) == EMBER_ZCL_STATUS_SUCCESS)
-        {
-            if (ourFeatureMap & to_underlying(Feature::kMultiSpeed))
-                multiSpeedSupported = true;
-        }
-    }
-
     switch (attributePath.mAttributeId)
     {
     case FanMode::Id: {
@@ -164,7 +286,7 @@
             VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
                            ChipLogError(Zcl, "Failed to write PercentCurrent with error: 0x%02x", status));
 
-            if (multiSpeedSupported)
+            if (SupportsMultiSpeed(attributePath.mEndpointId))
             {
                 status = SpeedSetting::Set(attributePath.mEndpointId, 0);
                 VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
@@ -185,7 +307,7 @@
             VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
                            ChipLogError(Zcl, "Failed to write PercentSetting with error: 0x%02x", status));
 
-            if (multiSpeedSupported)
+            if (SupportsMultiSpeed(attributePath.mEndpointId))
             {
                 gWriteFromClusterLogic = true;
                 status                 = SpeedSetting::SetNull(attributePath.mEndpointId);
@@ -208,7 +330,7 @@
                            ChipLogError(Zcl, "Failed to set FanMode to off with error: 0x%02x", status));
         }
 
-        if (multiSpeedSupported)
+        if (SupportsMultiSpeed(attributePath.mEndpointId))
         {
             // Adjust SpeedSetting from a percent value change for PercentSetting
             // speed = ceil( SpeedMax * (percent * 0.01) )
@@ -231,53 +353,69 @@
                 VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
                                ChipLogError(Zcl, "Failed to set SpeedSetting with error: 0x%02x", status));
             }
-
-            status = SpeedCurrent::Set(attributePath.mEndpointId, speedSetting);
-            VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
-                           ChipLogError(Zcl, "Failed to set SpeedCurrent with error: 0x%02x", status));
         }
         break;
     }
     case SpeedSetting::Id: {
-        DataModel::Nullable<uint8_t> speedSetting;
-        EmberAfStatus status = SpeedSetting::Get(attributePath.mEndpointId, speedSetting);
-        VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status && !speedSetting.IsNull());
-
-        // If SpeedSetting is set to 0, the server SHALL set the FanMode attribute value to Off.
-        if (speedSetting.Value() == 0)
+        if (SupportsMultiSpeed(attributePath.mEndpointId))
         {
-            status = SetFanModeToOff(attributePath.mEndpointId);
+            DataModel::Nullable<uint8_t> speedSetting;
+            EmberAfStatus status = SpeedSetting::Get(attributePath.mEndpointId, speedSetting);
+            VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status && !speedSetting.IsNull());
+
+            // If SpeedSetting is set to 0, the server SHALL set the FanMode attribute value to Off.
+            if (speedSetting.Value() == 0)
+            {
+                status = SetFanModeToOff(attributePath.mEndpointId);
+                VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
+                               ChipLogError(Zcl, "Failed to set FanMode to off with error: 0x%02x", status));
+            }
+
+            // Adjust PercentSetting from a speed value change for SpeedSetting
+            // percent = floor( speed/SpeedMax * 100 )
+            uint8_t speedMax;
+            status = SpeedMax::Get(attributePath.mEndpointId, &speedMax);
             VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
-                           ChipLogError(Zcl, "Failed to set FanMode to off with error: 0x%02x", status));
-        }
+                           ChipLogError(Zcl, "Failed to get SpeedMax with error: 0x%02x", status));
 
-        // Adjust PercentSetting from a speed value change for SpeedSetting
-        // percent = floor( speed/SpeedMax * 100 )
-        uint8_t speedMax;
-        status = SpeedMax::Get(attributePath.mEndpointId, &speedMax);
-        VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(Zcl, "Failed to get SpeedMax with error: 0x%02x", status));
-
-        DataModel::Nullable<uint8_t> currentPercentSetting;
-        status = PercentSetting::Get(attributePath.mEndpointId, currentPercentSetting);
-        VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
-                       ChipLogError(Zcl, "Failed to get PercentSetting with error: 0x%02x", status));
-
-        float speed            = speedSetting.Value();
-        uint8_t percentSetting = static_cast<uint8_t>(speed / speedMax * 100);
-
-        if (currentPercentSetting.IsNull() || percentSetting != currentPercentSetting.Value())
-        {
-            status = PercentSetting::Set(attributePath.mEndpointId, percentSetting);
+            DataModel::Nullable<uint8_t> currentPercentSetting;
+            status = PercentSetting::Get(attributePath.mEndpointId, currentPercentSetting);
             VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
-                           ChipLogError(Zcl, "Failed to set PercentSetting with error: 0x%02x", status));
-        }
+                           ChipLogError(Zcl, "Failed to get PercentSetting with error: 0x%02x", status));
 
-        status = PercentCurrent::Set(attributePath.mEndpointId, percentSetting);
-        VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
-                       ChipLogError(Zcl, "Failed to set PercentCurrent with error: 0x%02x", status));
+            float speed            = speedSetting.Value();
+            uint8_t percentSetting = static_cast<uint8_t>(speed / speedMax * 100);
+
+            if (currentPercentSetting.IsNull() || percentSetting != currentPercentSetting.Value())
+            {
+                status = PercentSetting::Set(attributePath.mEndpointId, percentSetting);
+                VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status,
+                               ChipLogError(Zcl, "Failed to set PercentSetting with error: 0x%02x", status));
+            }
+        }
         break;
     }
     default:
         break;
     }
 }
+
+bool emberAfFanControlClusterStepCallback(chip::app::CommandHandler * commandObj,
+                                          const chip::app::ConcreteCommandPath & commandPath,
+                                          const chip::app::Clusters::FanControl::Commands::Step::DecodableType & commandData)
+{
+    /*
+     * TODO: Clarification needed in spec issue #6496 - if this is tied to the SpeedSetting attribute, then
+     * the attribute can be updated here, if it is supposed to be implementation specific, then the command
+     * will have to be handed off to an application specific callback which will require some sort of delegate.
+     */
+
+    Protocols::InteractionModel::Status status = Status::Success;
+
+    if (!SupportsStep(commandPath.mEndpointId))
+    {
+        status = Status::UnsupportedCommand;
+    }
+    commandObj->AddStatus(commandPath, status);
+    return true;
+}
diff --git a/src/app/zap-templates/zcl/data-model/chip/fan-control-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/fan-control-cluster.xml
index ce9ef44..8460756 100644
--- a/src/app/zap-templates/zcl/data-model/chip/fan-control-cluster.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/fan-control-cluster.xml
@@ -23,6 +23,8 @@
     <field name="Auto" mask="0x02" />
     <field name="Rocking" mask="0x04" />
     <field name="Wind" mask="0x08" />
+    <field name="Step" mask="0x10" />
+    <field name="Airflow Direction" mask="0x20" />
   </bitmap>
 
   <enum name="FanModeType" type="ENUM8">
@@ -65,6 +67,18 @@
     <field name="Natural Wind" mask="0x02" />
   </bitmap>
 
+  <enum name="DirectionEnum" type="ENUM8">
+    <cluster code="0x0202"/>
+    <item name="Increase" value="0x00"/>
+    <item name="Decrease" value="0x01"/>
+  </enum>
+
+  <enum name="AirflowDirectionEnum" type="ENUM8">
+    <cluster code="0x0202"/>
+    <item name="Forward" value="0x00"/>
+    <item name="Reverse" value="0x01"/>
+  </enum>
+
   <cluster>
     <name>Fan Control</name>
     <domain>HVAC</domain>
@@ -86,5 +100,14 @@
     <attribute side="server" code="0x0008" define="ROCK_SETTING" type="BITMAP8" writable="true" default="0x00" optional="true">RockSetting</attribute>
     <attribute side="server" code="0x0009" define="WIND_SUPPORT" type="BITMAP8" writable="false" default="0x00" optional="true">WindSupport</attribute>
     <attribute side="server" code="0x000A" define="WIND_SETTING" type="BITMAP8" writable="true" default="0x00" optional="true">WindSetting</attribute>
+    <attribute side="server" code="0x000B" define="AIRFLOW_DIRECTION" type="AirflowDirectionEnum" writable="true" default="0" optional="true">AirflowDirection</attribute>
+  
+    <command source="client" code="0x00" name="Step" optional="true">
+      <description>The Step command speeds up or slows down the fan, in steps.</description>
+      <arg name="Direction" type="DirectionEnum"/>
+      <arg name="Wrap" type="boolean" optional="true"/>
+      <arg name="LowestOff" type="boolean" optional="true"/>
+    </command>
+
   </cluster>
 </configurator>
diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter
index ed238fa..f8bf495 100644
--- a/src/controller/data_model/controller-clusters.matter
+++ b/src/controller/data_model/controller-clusters.matter
@@ -4519,6 +4519,16 @@
 
 /** An interface for controlling a fan in a heating/cooling system. */
 client cluster FanControl = 514 {
+  enum AirflowDirectionEnum : ENUM8 {
+    kForward = 0;
+    kReverse = 1;
+  }
+
+  enum DirectionEnum : ENUM8 {
+    kIncrease = 0;
+    kDecrease = 1;
+  }
+
   enum FanModeSequenceType : ENUM8 {
     kOffLowMedHigh = 0;
     kOffLowHigh = 1;
@@ -4543,6 +4553,8 @@
     kAuto = 0x2;
     kRocking = 0x4;
     kWind = 0x8;
+    kStep = 0x10;
+    kAirflowDirection = 0x20;
   }
 
   bitmap RockSupportMask : BITMAP8 {
@@ -4572,12 +4584,22 @@
   attribute optional bitmap8 rockSetting = 8;
   readonly attribute optional bitmap8 windSupport = 9;
   attribute optional bitmap8 windSetting = 10;
+  attribute optional AirflowDirectionEnum airflowDirection = 11;
   readonly attribute command_id generatedCommandList[] = 65528;
   readonly attribute command_id acceptedCommandList[] = 65529;
   readonly attribute event_id eventList[] = 65530;
   readonly attribute attrib_id attributeList[] = 65531;
   readonly attribute bitmap32 featureMap = 65532;
   readonly attribute int16u clusterRevision = 65533;
+
+  request struct StepRequest {
+    DirectionEnum direction = 0;
+    optional boolean wrap = 1;
+    optional boolean lowestOff = 2;
+  }
+
+  /** The Step command speeds up or slows down the fan, in steps. */
+  command Step(StepRequest): DefaultSuccess = 0;
 }
 
 /** An interface for configuring the user interface of a thermostat (which may be remote from the thermostat). */
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
index 63b53c7..d558f16 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
@@ -9328,6 +9328,7 @@
             RockSetting(8L),
             WindSupport(9L),
             WindSetting(10L),
+            AirflowDirection(11L),
             GeneratedCommandList(65528L),
             AcceptedCommandList(65529L),
             EventList(65530L),
@@ -9373,7 +9374,8 @@
             }
         }
 
-        public enum Command {;
+        public enum Command {
+            Step(0L),;
             private final long id;
             Command(long id) {
                 this.id = id;
@@ -9391,7 +9393,24 @@
                 }
                 throw new NoSuchFieldError();
             }
-        }@Override
+        }public enum StepCommandField {Direction(0),Wrap(1),LowestOff(2),;
+                    private final int id;
+                    StepCommandField(int id) {
+                        this.id = id;
+                    }
+
+                    public int getID() {
+                        return id;
+                    }
+                    public static StepCommandField value(int id) throws NoSuchFieldError {
+                        for (StepCommandField field : StepCommandField.values()) {
+                        if (field.getID() == id) {
+                            return field;
+                        }
+                        }
+                        throw new NoSuchFieldError();
+                    }
+                }@Override
         public String getAttributeName(long id) throws NoSuchFieldError {
             return Attribute.value(id).toString();
         }
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java
index acf9c51..3678b7d 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java
@@ -10072,6 +10072,17 @@
           readFanControlWindSettingCommandParams
         );
         result.put("readWindSettingAttribute", readFanControlWindSettingAttributeInteractionInfo);
+     Map<String, CommandParameterInfo> readFanControlAirflowDirectionCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
+        InteractionInfo readFanControlAirflowDirectionAttributeInteractionInfo = new InteractionInfo(
+          (cluster, callback, commandArguments) -> {
+            ((ChipClusters.FanControlCluster) cluster).readAirflowDirectionAttribute(
+              (ChipClusters.IntegerAttributeCallback) callback
+            );
+          },
+          () -> new ClusterInfoMapping.DelegatedIntegerAttributeCallback(),
+          readFanControlAirflowDirectionCommandParams
+        );
+        result.put("readAirflowDirectionAttribute", readFanControlAirflowDirectionAttributeInteractionInfo);
      Map<String, CommandParameterInfo> readFanControlGeneratedCommandListCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
         InteractionInfo readFanControlGeneratedCommandListAttributeInteractionInfo = new InteractionInfo(
           (cluster, callback, commandArguments) -> {
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
index 508627d..4f517da 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
@@ -2223,6 +2223,28 @@
       writeFanControlWindSettingCommandParams
     );
     writeFanControlInteractionInfo.put("writeWindSettingAttribute", writeFanControlWindSettingAttributeInteractionInfo);
+    Map<String, CommandParameterInfo> writeFanControlAirflowDirectionCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
+    CommandParameterInfo fanControlairflowDirectionCommandParameterInfo =
+        new CommandParameterInfo(
+            "value", 
+            Integer.class, 
+            Integer.class 
+        );
+    writeFanControlAirflowDirectionCommandParams.put(
+        "value",
+        fanControlairflowDirectionCommandParameterInfo
+    );
+    InteractionInfo writeFanControlAirflowDirectionAttributeInteractionInfo = new InteractionInfo(
+      (cluster, callback, commandArguments) -> {
+        ((ChipClusters.FanControlCluster) cluster).writeAirflowDirectionAttribute(
+          (DefaultClusterCallback) callback,
+          (Integer) commandArguments.get("value")
+        );
+      },
+      () -> new ClusterInfoMapping.DelegatedDefaultClusterCallback(),
+      writeFanControlAirflowDirectionCommandParams
+    );
+    writeFanControlInteractionInfo.put("writeAirflowDirectionAttribute", writeFanControlAirflowDirectionAttributeInteractionInfo);
     writeAttributeMap.put("fanControl", writeFanControlInteractionInfo);
     Map<String, InteractionInfo> writeThermostatUserInterfaceConfigurationInteractionInfo = new LinkedHashMap<>();
     Map<String, CommandParameterInfo> writeThermostatUserInterfaceConfigurationTemperatureDisplayModeCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
diff --git a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
index db04f23..bed47f6 100644
--- a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
+++ b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
@@ -19055,6 +19055,21 @@
                                                                           cppValue, value);
             return value;
         }
+        case Attributes::AirflowDirection::Id: {
+            using TypeInfo = Attributes::AirflowDirection::TypeInfo;
+            TypeInfo::DecodableType cppValue;
+            *aError = app::DataModel::Decode(aReader, cppValue);
+            if (*aError != CHIP_NO_ERROR)
+            {
+                return nullptr;
+            }
+            jobject value;
+            std::string valueClassName     = "java/lang/Integer";
+            std::string valueCtorSignature = "(I)V";
+            chip::JniReferences::GetInstance().CreateBoxedObject<uint8_t>(valueClassName.c_str(), valueCtorSignature.c_str(),
+                                                                          static_cast<uint8_t>(cppValue), value);
+            return value;
+        }
         case Attributes::GeneratedCommandList::Id: {
             using TypeInfo = Attributes::GeneratedCommandList::TypeInfo;
             TypeInfo::DecodableType cppValue;
diff --git a/src/controller/java/zap-generated/CHIPClustersWrite-JNI.cpp b/src/controller/java/zap-generated/CHIPClustersWrite-JNI.cpp
index 3b922d4..14111e7 100644
--- a/src/controller/java/zap-generated/CHIPClustersWrite-JNI.cpp
+++ b/src/controller/java/zap-generated/CHIPClustersWrite-JNI.cpp
@@ -5722,6 +5722,58 @@
     onFailure.release();
 }
 
+JNI_METHOD(void, FanControlCluster, writeAirflowDirectionAttribute)
+(JNIEnv * env, jobject self, jlong clusterPtr, jobject callback, jobject value, jobject timedWriteTimeoutMs)
+{
+    chip::DeviceLayer::StackLock lock;
+    ListFreer listFreer;
+    using TypeInfo = chip::app::Clusters::FanControl::Attributes::AirflowDirection::TypeInfo;
+    TypeInfo::Type cppValue;
+
+    std::vector<Platform::UniquePtr<JniByteArray>> cleanupByteArrays;
+    std::vector<Platform::UniquePtr<JniUtfString>> cleanupStrings;
+
+    cppValue =
+        static_cast<std::remove_reference_t<decltype(cppValue)>>(chip::JniReferences::GetInstance().IntegerToPrimitive(value));
+
+    std::unique_ptr<CHIPDefaultSuccessCallback, void (*)(CHIPDefaultSuccessCallback *)> onSuccess(
+        Platform::New<CHIPDefaultSuccessCallback>(callback), Platform::Delete<CHIPDefaultSuccessCallback>);
+    VerifyOrReturn(onSuccess.get() != nullptr,
+                   chip::AndroidClusterExceptions::GetInstance().ReturnIllegalStateException(
+                       env, callback, "Error creating native success callback", CHIP_ERROR_NO_MEMORY));
+
+    std::unique_ptr<CHIPDefaultFailureCallback, void (*)(CHIPDefaultFailureCallback *)> onFailure(
+        Platform::New<CHIPDefaultFailureCallback>(callback), Platform::Delete<CHIPDefaultFailureCallback>);
+    VerifyOrReturn(onFailure.get() != nullptr,
+                   chip::AndroidClusterExceptions::GetInstance().ReturnIllegalStateException(
+                       env, callback, "Error creating native failure callback", CHIP_ERROR_NO_MEMORY));
+
+    CHIP_ERROR err                 = CHIP_NO_ERROR;
+    FanControlCluster * cppCluster = reinterpret_cast<FanControlCluster *>(clusterPtr);
+    VerifyOrReturn(cppCluster != nullptr,
+                   chip::AndroidClusterExceptions::GetInstance().ReturnIllegalStateException(
+                       env, callback, "Could not get native cluster", CHIP_ERROR_INCORRECT_STATE));
+
+    auto successFn = chip::Callback::Callback<CHIPDefaultWriteSuccessCallbackType>::FromCancelable(onSuccess->Cancel());
+    auto failureFn = chip::Callback::Callback<CHIPDefaultFailureCallbackType>::FromCancelable(onFailure->Cancel());
+
+    if (timedWriteTimeoutMs == nullptr)
+    {
+        err = cppCluster->WriteAttribute<TypeInfo>(cppValue, onSuccess->mContext, successFn->mCall, failureFn->mCall);
+    }
+    else
+    {
+        err = cppCluster->WriteAttribute<TypeInfo>(cppValue, onSuccess->mContext, successFn->mCall, failureFn->mCall,
+                                                   chip::JniReferences::GetInstance().IntegerToPrimitive(timedWriteTimeoutMs));
+    }
+    VerifyOrReturn(
+        err == CHIP_NO_ERROR,
+        chip::AndroidClusterExceptions::GetInstance().ReturnIllegalStateException(env, callback, "Error writing attribute", err));
+
+    onSuccess.release();
+    onFailure.release();
+}
+
 JNI_METHOD(void, ThermostatUserInterfaceConfigurationCluster, writeTemperatureDisplayModeAttribute)
 (JNIEnv * env, jobject self, jlong clusterPtr, jobject callback, jobject value, jobject timedWriteTimeoutMs)
 {
diff --git a/src/controller/java/zap-generated/chip/devicecontroller/ChipClusters.java b/src/controller/java/zap-generated/chip/devicecontroller/ChipClusters.java
index afc88c2..d689415 100644
--- a/src/controller/java/zap-generated/chip/devicecontroller/ChipClusters.java
+++ b/src/controller/java/zap-generated/chip/devicecontroller/ChipClusters.java
@@ -22720,6 +22720,24 @@
       subscribeWindSettingAttribute(chipClusterPtr, callback, minInterval, maxInterval);
     }
 
+    public void readAirflowDirectionAttribute(IntegerAttributeCallback callback) {
+      readAirflowDirectionAttribute(chipClusterPtr, callback);
+    }
+
+    public void writeAirflowDirectionAttribute(DefaultClusterCallback callback, Integer value) {
+      writeAirflowDirectionAttribute(chipClusterPtr, callback, value, null);
+    }
+
+    public void writeAirflowDirectionAttribute(
+        DefaultClusterCallback callback, Integer value, int timedWriteTimeoutMs) {
+      writeAirflowDirectionAttribute(chipClusterPtr, callback, value, timedWriteTimeoutMs);
+    }
+
+    public void subscribeAirflowDirectionAttribute(
+        IntegerAttributeCallback callback, int minInterval, int maxInterval) {
+      subscribeAirflowDirectionAttribute(chipClusterPtr, callback, minInterval, maxInterval);
+    }
+
     public void readGeneratedCommandListAttribute(GeneratedCommandListAttributeCallback callback) {
       readGeneratedCommandListAttribute(chipClusterPtr, callback);
     }
@@ -22882,6 +22900,18 @@
     private native void subscribeWindSettingAttribute(
         long chipClusterPtr, IntegerAttributeCallback callback, int minInterval, int maxInterval);
 
+    private native void readAirflowDirectionAttribute(
+        long chipClusterPtr, IntegerAttributeCallback callback);
+
+    private native void writeAirflowDirectionAttribute(
+        long chipClusterPtr,
+        DefaultClusterCallback callback,
+        Integer value,
+        @Nullable Integer timedWriteTimeoutMs);
+
+    private native void subscribeAirflowDirectionAttribute(
+        long chipClusterPtr, IntegerAttributeCallback callback, int minInterval, int maxInterval);
+
     private native void readGeneratedCommandListAttribute(
         long chipClusterPtr, GeneratedCommandListAttributeCallback callback);
 
diff --git a/src/controller/java/zap-generated/chip/devicecontroller/ChipIdLookup.java b/src/controller/java/zap-generated/chip/devicecontroller/ChipIdLookup.java
index f0281a9..7698da8 100644
--- a/src/controller/java/zap-generated/chip/devicecontroller/ChipIdLookup.java
+++ b/src/controller/java/zap-generated/chip/devicecontroller/ChipIdLookup.java
@@ -3297,6 +3297,9 @@
       if (attributeId == 10L) {
         return "WindSetting";
       }
+      if (attributeId == 11L) {
+        return "AirflowDirection";
+      }
       if (attributeId == 65528L) {
         return "GeneratedCommandList";
       }
diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py
index 245a048..06c2590 100644
--- a/src/controller/python/chip/clusters/CHIPClusters.py
+++ b/src/controller/python/chip/clusters/CHIPClusters.py
@@ -7054,6 +7054,15 @@
         "clusterName": "FanControl",
         "clusterId": 0x00000202,
         "commands": {
+            0x00000000: {
+                "commandId": 0x00000000,
+                "commandName": "Step",
+                "args": {
+                    "direction": "int",
+                    "wrap": "bool",
+                    "lowestOff": "bool",
+                },
+            },
         },
         "attributes": {
             0x00000000: {
@@ -7128,6 +7137,13 @@
                 "reportable": True,
                 "writable": True,
             },
+            0x0000000B: {
+                "attributeName": "AirflowDirection",
+                "attributeId": 0x0000000B,
+                "type": "int",
+                "reportable": True,
+                "writable": True,
+            },
             0x0000FFF8: {
                 "attributeName": "GeneratedCommandList",
                 "attributeId": 0x0000FFF8,
diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py
index 0afd782..ae4d95e 100644
--- a/src/controller/python/chip/clusters/Objects.py
+++ b/src/controller/python/chip/clusters/Objects.py
@@ -24539,6 +24539,7 @@
                 ClusterObjectFieldDescriptor(Label="rockSetting", Tag=0x00000008, Type=typing.Optional[uint]),
                 ClusterObjectFieldDescriptor(Label="windSupport", Tag=0x00000009, Type=typing.Optional[uint]),
                 ClusterObjectFieldDescriptor(Label="windSetting", Tag=0x0000000A, Type=typing.Optional[uint]),
+                ClusterObjectFieldDescriptor(Label="airflowDirection", Tag=0x0000000B, Type=typing.Optional[FanControl.Enums.AirflowDirectionEnum]),
                 ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]),
                 ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]),
                 ClusterObjectFieldDescriptor(Label="eventList", Tag=0x0000FFFA, Type=typing.List[uint]),
@@ -24558,6 +24559,7 @@
     rockSetting: 'typing.Optional[uint]' = None
     windSupport: 'typing.Optional[uint]' = None
     windSetting: 'typing.Optional[uint]' = None
+    airflowDirection: 'typing.Optional[FanControl.Enums.AirflowDirectionEnum]' = None
     generatedCommandList: 'typing.List[uint]' = None
     acceptedCommandList: 'typing.List[uint]' = None
     eventList: 'typing.List[uint]' = None
@@ -24566,6 +24568,24 @@
     clusterRevision: 'uint' = None
 
     class Enums:
+        class AirflowDirectionEnum(MatterIntEnum):
+            kForward = 0x00
+            kReverse = 0x01
+            # All received enum values that are not listed above will be mapped
+            # to kUnknownEnumValue. This is a helper enum value that should only
+            # be used by code to process how it handles receiving and unknown
+            # enum value. This specific should never be transmitted.
+            kUnknownEnumValue = 2,
+
+        class DirectionEnum(MatterIntEnum):
+            kIncrease = 0x00
+            kDecrease = 0x01
+            # All received enum values that are not listed above will be mapped
+            # to kUnknownEnumValue. This is a helper enum value that should only
+            # be used by code to process how it handles receiving and unknown
+            # enum value. This specific should never be transmitted.
+            kUnknownEnumValue = 2,
+
         class FanModeSequenceType(MatterIntEnum):
             kOffLowMedHigh = 0x00
             kOffLowHigh = 0x01
@@ -24599,6 +24619,8 @@
             kAuto = 0x2
             kRocking = 0x4
             kWind = 0x8
+            kStep = 0x10
+            kAirflowDirection = 0x20
 
         class RockSupportMask(IntFlag):
             kRockLeftRight = 0x1
@@ -24613,6 +24635,27 @@
             kSleepWind = 0x1
             kNaturalWind = 0x2
 
+    class Commands:
+        @dataclass
+        class Step(ClusterCommand):
+            cluster_id: typing.ClassVar[int] = 0x0202
+            command_id: typing.ClassVar[int] = 0x00000000
+            is_client: typing.ClassVar[bool] = True
+            response_type: typing.ClassVar[str] = None
+
+            @ChipUtility.classproperty
+            def descriptor(cls) -> ClusterObjectDescriptor:
+                return ClusterObjectDescriptor(
+                    Fields=[
+                        ClusterObjectFieldDescriptor(Label="direction", Tag=0, Type=FanControl.Enums.DirectionEnum),
+                        ClusterObjectFieldDescriptor(Label="wrap", Tag=1, Type=typing.Optional[bool]),
+                        ClusterObjectFieldDescriptor(Label="lowestOff", Tag=2, Type=typing.Optional[bool]),
+                    ])
+
+            direction: 'FanControl.Enums.DirectionEnum' = 0
+            wrap: 'typing.Optional[bool]' = None
+            lowestOff: 'typing.Optional[bool]' = None
+
     class Attributes:
         @dataclass
         class FanMode(ClusterAttributeDescriptor):
@@ -24791,6 +24834,22 @@
             value: 'typing.Optional[uint]' = None
 
         @dataclass
+        class AirflowDirection(ClusterAttributeDescriptor):
+            @ChipUtility.classproperty
+            def cluster_id(cls) -> int:
+                return 0x0202
+
+            @ChipUtility.classproperty
+            def attribute_id(cls) -> int:
+                return 0x0000000B
+
+            @ChipUtility.classproperty
+            def attribute_type(cls) -> ClusterObjectFieldDescriptor:
+                return ClusterObjectFieldDescriptor(Type=typing.Optional[FanControl.Enums.AirflowDirectionEnum])
+
+            value: 'typing.Optional[FanControl.Enums.AirflowDirectionEnum]' = None
+
+        @dataclass
         class GeneratedCommandList(ClusterAttributeDescriptor):
             @ChipUtility.classproperty
             def cluster_id(cls) -> int:
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm
index 5dda77c..4e92c0b 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm
@@ -3081,6 +3081,9 @@
     case Attributes::WindSetting::Id: {
         return YES;
     }
+    case Attributes::AirflowDirection::Id: {
+        return YES;
+    }
     case Attributes::GeneratedCommandList::Id: {
         return YES;
     }
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
index e65888c..9d6b660 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
@@ -15082,6 +15082,17 @@
         value = [NSNumber numberWithUnsignedChar:cppValue];
         return value;
     }
+    case Attributes::AirflowDirection::Id: {
+        using TypeInfo = Attributes::AirflowDirection::TypeInfo;
+        TypeInfo::DecodableType cppValue;
+        *aError = DataModel::Decode(aReader, cppValue);
+        if (*aError != CHIP_NO_ERROR) {
+            return nil;
+        }
+        NSNumber * _Nonnull value;
+        value = [NSNumber numberWithUnsignedChar:chip::to_underlying(cppValue)];
+        return value;
+    }
     case Attributes::GeneratedCommandList::Id: {
         using TypeInfo = Attributes::GeneratedCommandList::TypeInfo;
         TypeInfo::DecodableType cppValue;
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
index 204bbf8..a215e2e 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
@@ -13584,6 +13584,13 @@
                                    queue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER
     API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 
+/**
+ * Command Step
+ *
+ * The Step command speeds up or slows down the fan, in steps.
+ */
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params completion:(MTRStatusCompletion)completion MTR_NEWLY_AVAILABLE;
+
 - (void)readAttributeFanModeWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
     API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 - (void)writeAttributeFanModeWithValue:(NSNumber * _Nonnull)value
@@ -13759,6 +13766,23 @@
                                            completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
     API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 
+- (void)readAttributeAirflowDirectionWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
+    MTR_NEWLY_AVAILABLE;
+- (void)writeAttributeAirflowDirectionWithValue:(NSNumber * _Nonnull)value
+                                     completion:(MTRStatusCompletion)completion MTR_NEWLY_AVAILABLE;
+- (void)writeAttributeAirflowDirectionWithValue:(NSNumber * _Nonnull)value
+                                         params:(MTRWriteParams * _Nullable)params
+                                     completion:(MTRStatusCompletion)completion MTR_NEWLY_AVAILABLE;
+- (void)subscribeAttributeAirflowDirectionWithParams:(MTRSubscribeParams *)params
+                             subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
+                                       reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler
+    MTR_NEWLY_AVAILABLE;
++ (void)readAttributeAirflowDirectionWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer
+                                                  endpoint:(NSNumber *)endpoint
+                                                     queue:(dispatch_queue_t)queue
+                                                completion:(void (^)(NSNumber * _Nullable value,
+                                                               NSError * _Nullable error))completion MTR_NEWLY_AVAILABLE;
+
 - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion
     API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 - (void)subscribeAttributeGeneratedCommandListWithParams:(MTRSubscribeParams *)params
@@ -32847,6 +32871,16 @@
     = 0x2,
 } API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 
+typedef NS_ENUM(uint8_t, MTRFanControlAirflowDirection) {
+    MTRFanControlAirflowDirectionForward MTR_NEWLY_AVAILABLE = 0x00,
+    MTRFanControlAirflowDirectionReverse MTR_NEWLY_AVAILABLE = 0x01,
+} MTR_NEWLY_AVAILABLE;
+
+typedef NS_ENUM(uint8_t, MTRFanControlDirection) {
+    MTRFanControlDirectionIncrease MTR_NEWLY_AVAILABLE = 0x00,
+    MTRFanControlDirectionDecrease MTR_NEWLY_AVAILABLE = 0x01,
+} MTR_NEWLY_AVAILABLE;
+
 typedef NS_ENUM(uint8_t, MTRFanControlFanModeSequenceType) {
     MTRFanControlFanModeSequenceTypeOffLowMedHigh API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x00,
     MTRFanControlFanModeSequenceTypeOffLowHigh API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x01,
@@ -32871,6 +32905,8 @@
     MTRFanControlFeatureAuto API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x2,
     MTRFanControlFeatureRocking API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x4,
     MTRFanControlFeatureWind API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) = 0x8,
+    MTRFanControlFeatureStep MTR_NEWLY_AVAILABLE = 0x10,
+    MTRFanControlFeatureAirflowDirection MTR_NEWLY_AVAILABLE = 0x20,
 } API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 
 typedef NS_OPTIONS(uint8_t, MTRFanControlRockSupportMask) {
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
index b9045be..62d1149 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
@@ -76146,6 +76146,51 @@
     return self;
 }
 
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params completion:(MTRStatusCompletion)completion
+{
+    // Make a copy of params before we go async.
+    params = [params copy];
+    auto * bridge = new MTRCommandSuccessCallbackBridge(
+        self.callbackQueue,
+        ^(id _Nullable value, NSError * _Nullable error) {
+            completion(error);
+        },
+        ^(ExchangeManager & exchangeManager, const SessionHandle & session, CommandSuccessCallbackType successCb,
+            MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
+            auto * typedBridge = static_cast<MTRCommandSuccessCallbackBridge *>(bridge);
+            Optional<uint16_t> timedInvokeTimeoutMs;
+            Optional<Timeout> invokeTimeout;
+            ListFreer listFreer;
+            FanControl::Commands::Step::Type request;
+            if (params != nil) {
+                if (params.timedInvokeTimeoutMs != nil) {
+                    params.timedInvokeTimeoutMs = MTRClampedNumber(params.timedInvokeTimeoutMs, @(1), @(UINT16_MAX));
+                    timedInvokeTimeoutMs.SetValue(params.timedInvokeTimeoutMs.unsignedShortValue);
+                }
+                if (params.serverSideProcessingTimeout != nil) {
+                    // Clamp to a number of seconds that will not overflow 32-bit
+                    // int when converted to ms.
+                    auto * serverSideProcessingTimeout = MTRClampedNumber(params.serverSideProcessingTimeout, @(0), @(UINT16_MAX));
+                    invokeTimeout.SetValue(Seconds16(serverSideProcessingTimeout.unsignedShortValue));
+                }
+            }
+            request.direction
+                = static_cast<std::remove_reference_t<decltype(request.direction)>>(params.direction.unsignedCharValue);
+            if (params.wrap != nil) {
+                auto & definedValue_0 = request.wrap.Emplace();
+                definedValue_0 = params.wrap.boolValue;
+            }
+            if (params.lowestOff != nil) {
+                auto & definedValue_0 = request.lowestOff.Emplace();
+                definedValue_0 = params.lowestOff.boolValue;
+            }
+
+            return MTRStartInvokeInteraction(typedBridge, request, exchangeManager, session, successCb, failureCb, self->_endpoint,
+                timedInvokeTimeoutMs, invokeTimeout);
+        });
+    std::move(*bridge).DispatchAction(self.device);
+}
+
 - (void)readAttributeFanModeWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
 {
     MTRReadParams * params = [[MTRReadParams alloc] init];
@@ -76852,6 +76897,87 @@
         });
 }
 
+- (void)readAttributeAirflowDirectionWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
+{
+    MTRReadParams * params = [[MTRReadParams alloc] init];
+    using TypeInfo = FanControl::Attributes::AirflowDirection::TypeInfo;
+    return MTRReadAttribute<MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge, NSNumber, TypeInfo::DecodableType>(
+        params, completion, self.callbackQueue, self.device, self->_endpoint, TypeInfo::GetClusterId(), TypeInfo::GetAttributeId());
+}
+
+- (void)writeAttributeAirflowDirectionWithValue:(NSNumber * _Nonnull)value completion:(MTRStatusCompletion)completion
+{
+    [self writeAttributeAirflowDirectionWithValue:(NSNumber * _Nonnull) value params:nil completion:completion];
+}
+- (void)writeAttributeAirflowDirectionWithValue:(NSNumber * _Nonnull)value
+                                         params:(MTRWriteParams * _Nullable)params
+                                     completion:(MTRStatusCompletion)completion
+{
+    // Make a copy of params before we go async.
+    params = [params copy];
+    value = [value copy];
+
+    auto * bridge = new MTRDefaultSuccessCallbackBridge(
+        self.callbackQueue,
+        ^(id _Nullable ignored, NSError * _Nullable error) {
+            completion(error);
+        },
+        ^(ExchangeManager & exchangeManager, const SessionHandle & session, DefaultSuccessCallbackType successCb,
+            MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
+            chip::Optional<uint16_t> timedWriteTimeout;
+            if (params != nil) {
+                if (params.timedWriteTimeout != nil) {
+                    timedWriteTimeout.SetValue(params.timedWriteTimeout.unsignedShortValue);
+                }
+            }
+
+            ListFreer listFreer;
+            using TypeInfo = FanControl::Attributes::AirflowDirection::TypeInfo;
+            TypeInfo::Type cppValue;
+            cppValue = static_cast<std::remove_reference_t<decltype(cppValue)>>(value.unsignedCharValue);
+
+            chip::Controller::ClusterBase cppCluster(exchangeManager, session, self->_endpoint);
+            return cppCluster.WriteAttribute<TypeInfo>(cppValue, bridge, successCb, failureCb, timedWriteTimeout);
+        });
+    std::move(*bridge).DispatchAction(self.device);
+}
+
+- (void)subscribeAttributeAirflowDirectionWithParams:(MTRSubscribeParams * _Nonnull)params
+                             subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
+                                       reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler
+{
+    using TypeInfo = FanControl::Attributes::AirflowDirection::TypeInfo;
+    MTRSubscribeAttribute<MTRFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge, NSNumber,
+        TypeInfo::DecodableType>(params, subscriptionEstablished, reportHandler, self.callbackQueue, self.device, self->_endpoint,
+        TypeInfo::GetClusterId(), TypeInfo::GetAttributeId());
+}
+
++ (void)readAttributeAirflowDirectionWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer
+                                                  endpoint:(NSNumber *)endpoint
+                                                     queue:(dispatch_queue_t)queue
+                                                completion:
+                                                    (void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
+{
+    auto * bridge = new MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(queue, completion);
+    std::move(*bridge).DispatchLocalAction(clusterStateCacheContainer.baseDevice,
+        ^(FanControlClusterAirflowDirectionEnumAttributeCallback successCb, MTRErrorCallback failureCb) {
+            if (clusterStateCacheContainer.cppClusterStateCache) {
+                chip::app::ConcreteAttributePath path;
+                using TypeInfo = FanControl::Attributes::AirflowDirection::TypeInfo;
+                path.mEndpointId = static_cast<chip::EndpointId>([endpoint unsignedShortValue]);
+                path.mClusterId = TypeInfo::GetClusterId();
+                path.mAttributeId = TypeInfo::GetAttributeId();
+                TypeInfo::DecodableType value;
+                CHIP_ERROR err = clusterStateCacheContainer.cppClusterStateCache->Get<TypeInfo>(path, value);
+                if (err == CHIP_NO_ERROR) {
+                    successCb(bridge, value);
+                }
+                return err;
+            }
+            return CHIP_ERROR_NOT_FOUND;
+        });
+}
+
 - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion
 {
     MTRReadParams * params = [[MTRReadParams alloc] init];
@@ -77119,6 +77245,11 @@
 
 @implementation MTRBaseClusterFanControl (Deprecated)
 
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params completionHandler:(MTRStatusCompletion)completionHandler
+{
+    [self stepWithParams:params completion:completionHandler];
+}
+
 - (void)readAttributeFanModeWithCompletionHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completionHandler
 {
     [self readAttributeFanModeWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) {
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.h b/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.h
index c9022d3..b294998 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.h
@@ -595,6 +595,13 @@
                                                                        chip::app::Clusters::Thermostat::ThermostatSystemMode);
 typedef void (*NullableThermostatClusterThermostatSystemModeAttributeCallback)(
     void *, const chip::app::DataModel::Nullable<chip::app::Clusters::Thermostat::ThermostatSystemMode> &);
+typedef void (*FanControlClusterAirflowDirectionEnumAttributeCallback)(void *,
+                                                                       chip::app::Clusters::FanControl::AirflowDirectionEnum);
+typedef void (*NullableFanControlClusterAirflowDirectionEnumAttributeCallback)(
+    void *, const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::AirflowDirectionEnum> &);
+typedef void (*FanControlClusterDirectionEnumAttributeCallback)(void *, chip::app::Clusters::FanControl::DirectionEnum);
+typedef void (*NullableFanControlClusterDirectionEnumAttributeCallback)(
+    void *, const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::DirectionEnum> &);
 typedef void (*FanControlClusterFanModeSequenceTypeAttributeCallback)(void *, chip::app::Clusters::FanControl::FanModeSequenceType);
 typedef void (*NullableFanControlClusterFanModeSequenceTypeAttributeCallback)(
     void *, const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::FanModeSequenceType> &);
@@ -31597,6 +31604,140 @@
     MTRSubscriptionEstablishedHandler mEstablishedHandler;
 };
 
+class MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge
+    : public MTRCallbackBridge<FanControlClusterAirflowDirectionEnumAttributeCallback>
+{
+public:
+    MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler) :
+        MTRCallbackBridge<FanControlClusterAirflowDirectionEnumAttributeCallback>(queue, handler, OnSuccessFn){};
+
+    MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler,
+                                                                    MTRActionBlock action) :
+        MTRCallbackBridge<FanControlClusterAirflowDirectionEnumAttributeCallback>(queue, handler, action, OnSuccessFn){};
+
+    static void OnSuccessFn(void * context, chip::app::Clusters::FanControl::AirflowDirectionEnum value);
+};
+
+class MTRFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge
+    : public MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge
+{
+public:
+    MTRFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge(
+        dispatch_queue_t queue, ResponseHandler handler, MTRActionBlock action,
+        MTRSubscriptionEstablishedHandler establishedHandler) :
+        MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(queue, handler, action),
+        mEstablishedHandler(establishedHandler)
+    {}
+
+    void OnSubscriptionEstablished();
+    using MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::KeepAliveOnCallback;
+    using MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::OnDone;
+
+private:
+    MTRSubscriptionEstablishedHandler mEstablishedHandler;
+};
+
+class MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge
+    : public MTRCallbackBridge<NullableFanControlClusterAirflowDirectionEnumAttributeCallback>
+{
+public:
+    MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler) :
+        MTRCallbackBridge<NullableFanControlClusterAirflowDirectionEnumAttributeCallback>(queue, handler, OnSuccessFn){};
+
+    MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler,
+                                                                            MTRActionBlock action) :
+        MTRCallbackBridge<NullableFanControlClusterAirflowDirectionEnumAttributeCallback>(queue, handler, action, OnSuccessFn){};
+
+    static void OnSuccessFn(void * context,
+                            const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::AirflowDirectionEnum> & value);
+};
+
+class MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge
+    : public MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge
+{
+public:
+    MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge(
+        dispatch_queue_t queue, ResponseHandler handler, MTRActionBlock action,
+        MTRSubscriptionEstablishedHandler establishedHandler) :
+        MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge(queue, handler, action),
+        mEstablishedHandler(establishedHandler)
+    {}
+
+    void OnSubscriptionEstablished();
+    using MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::KeepAliveOnCallback;
+    using MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::OnDone;
+
+private:
+    MTRSubscriptionEstablishedHandler mEstablishedHandler;
+};
+
+class MTRFanControlClusterDirectionEnumAttributeCallbackBridge
+    : public MTRCallbackBridge<FanControlClusterDirectionEnumAttributeCallback>
+{
+public:
+    MTRFanControlClusterDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler) :
+        MTRCallbackBridge<FanControlClusterDirectionEnumAttributeCallback>(queue, handler, OnSuccessFn){};
+
+    MTRFanControlClusterDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler,
+                                                             MTRActionBlock action) :
+        MTRCallbackBridge<FanControlClusterDirectionEnumAttributeCallback>(queue, handler, action, OnSuccessFn){};
+
+    static void OnSuccessFn(void * context, chip::app::Clusters::FanControl::DirectionEnum value);
+};
+
+class MTRFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge
+    : public MTRFanControlClusterDirectionEnumAttributeCallbackBridge
+{
+public:
+    MTRFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge(dispatch_queue_t queue, ResponseHandler handler,
+                                                                         MTRActionBlock action,
+                                                                         MTRSubscriptionEstablishedHandler establishedHandler) :
+        MTRFanControlClusterDirectionEnumAttributeCallbackBridge(queue, handler, action),
+        mEstablishedHandler(establishedHandler)
+    {}
+
+    void OnSubscriptionEstablished();
+    using MTRFanControlClusterDirectionEnumAttributeCallbackBridge::KeepAliveOnCallback;
+    using MTRFanControlClusterDirectionEnumAttributeCallbackBridge::OnDone;
+
+private:
+    MTRSubscriptionEstablishedHandler mEstablishedHandler;
+};
+
+class MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge
+    : public MTRCallbackBridge<NullableFanControlClusterDirectionEnumAttributeCallback>
+{
+public:
+    MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler) :
+        MTRCallbackBridge<NullableFanControlClusterDirectionEnumAttributeCallback>(queue, handler, OnSuccessFn){};
+
+    MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge(dispatch_queue_t queue, ResponseHandler handler,
+                                                                     MTRActionBlock action) :
+        MTRCallbackBridge<NullableFanControlClusterDirectionEnumAttributeCallback>(queue, handler, action, OnSuccessFn){};
+
+    static void OnSuccessFn(void * context,
+                            const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::DirectionEnum> & value);
+};
+
+class MTRNullableFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge
+    : public MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge
+{
+public:
+    MTRNullableFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge(
+        dispatch_queue_t queue, ResponseHandler handler, MTRActionBlock action,
+        MTRSubscriptionEstablishedHandler establishedHandler) :
+        MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge(queue, handler, action),
+        mEstablishedHandler(establishedHandler)
+    {}
+
+    void OnSubscriptionEstablished();
+    using MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge::KeepAliveOnCallback;
+    using MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge::OnDone;
+
+private:
+    MTRSubscriptionEstablishedHandler mEstablishedHandler;
+};
+
 class MTRFanControlClusterFanModeSequenceTypeAttributeCallbackBridge
     : public MTRCallbackBridge<FanControlClusterFanModeSequenceTypeAttributeCallback>
 {
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.mm
index 1ab0b6f..fa6adc0 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCallbackBridge.mm
@@ -29505,6 +29505,106 @@
     }
 }
 
+void MTRFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::OnSuccessFn(
+    void * context, chip::app::Clusters::FanControl::AirflowDirectionEnum value)
+{
+    NSNumber * _Nonnull objCValue;
+    objCValue = [NSNumber numberWithUnsignedChar:chip::to_underlying(value)];
+    DispatchSuccess(context, objCValue);
+};
+
+void MTRFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge::OnSubscriptionEstablished()
+{
+    if (!mQueue) {
+        return;
+    }
+
+    if (mEstablishedHandler != nil) {
+        dispatch_async(mQueue, mEstablishedHandler);
+        // On failure, mEstablishedHandler will be cleaned up by our destructor,
+        // but we can clean it up earlier on successful subscription
+        // establishment.
+        mEstablishedHandler = nil;
+    }
+}
+
+void MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackBridge::OnSuccessFn(
+    void * context, const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::AirflowDirectionEnum> & value)
+{
+    NSNumber * _Nullable objCValue;
+    if (value.IsNull()) {
+        objCValue = nil;
+    } else {
+        objCValue = [NSNumber numberWithUnsignedChar:chip::to_underlying(value.Value())];
+    }
+    DispatchSuccess(context, objCValue);
+};
+
+void MTRNullableFanControlClusterAirflowDirectionEnumAttributeCallbackSubscriptionBridge::OnSubscriptionEstablished()
+{
+    if (!mQueue) {
+        return;
+    }
+
+    if (mEstablishedHandler != nil) {
+        dispatch_async(mQueue, mEstablishedHandler);
+        // On failure, mEstablishedHandler will be cleaned up by our destructor,
+        // but we can clean it up earlier on successful subscription
+        // establishment.
+        mEstablishedHandler = nil;
+    }
+}
+
+void MTRFanControlClusterDirectionEnumAttributeCallbackBridge::OnSuccessFn(
+    void * context, chip::app::Clusters::FanControl::DirectionEnum value)
+{
+    NSNumber * _Nonnull objCValue;
+    objCValue = [NSNumber numberWithUnsignedChar:chip::to_underlying(value)];
+    DispatchSuccess(context, objCValue);
+};
+
+void MTRFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge::OnSubscriptionEstablished()
+{
+    if (!mQueue) {
+        return;
+    }
+
+    if (mEstablishedHandler != nil) {
+        dispatch_async(mQueue, mEstablishedHandler);
+        // On failure, mEstablishedHandler will be cleaned up by our destructor,
+        // but we can clean it up earlier on successful subscription
+        // establishment.
+        mEstablishedHandler = nil;
+    }
+}
+
+void MTRNullableFanControlClusterDirectionEnumAttributeCallbackBridge::OnSuccessFn(
+    void * context, const chip::app::DataModel::Nullable<chip::app::Clusters::FanControl::DirectionEnum> & value)
+{
+    NSNumber * _Nullable objCValue;
+    if (value.IsNull()) {
+        objCValue = nil;
+    } else {
+        objCValue = [NSNumber numberWithUnsignedChar:chip::to_underlying(value.Value())];
+    }
+    DispatchSuccess(context, objCValue);
+};
+
+void MTRNullableFanControlClusterDirectionEnumAttributeCallbackSubscriptionBridge::OnSubscriptionEstablished()
+{
+    if (!mQueue) {
+        return;
+    }
+
+    if (mEstablishedHandler != nil) {
+        dispatch_async(mQueue, mEstablishedHandler);
+        // On failure, mEstablishedHandler will be cleaned up by our destructor,
+        // but we can clean it up earlier on successful subscription
+        // establishment.
+        mEstablishedHandler = nil;
+    }
+}
+
 void MTRFanControlClusterFanModeSequenceTypeAttributeCallbackBridge::OnSuccessFn(
     void * context, chip::app::Clusters::FanControl::FanModeSequenceType value)
 {
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
index 60ef563..aa7ddfa 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
@@ -5273,6 +5273,7 @@
     = 0x00000009,
     MTRAttributeIDTypeClusterFanControlAttributeWindSettingID API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
     = 0x0000000A,
+    MTRAttributeIDTypeClusterFanControlAttributeAirflowDirectionID MTR_NEWLY_AVAILABLE = 0x0000000B,
     MTRAttributeIDTypeClusterFanControlAttributeGeneratedCommandListID API_AVAILABLE(
         ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
     = MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID,
@@ -10474,6 +10475,11 @@
     MTRCommandIDTypeClusterThermostatCommandClearWeeklyScheduleID API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
     = 0x00000003,
 
+    // Cluster FanControl deprecated command id names
+
+    // Cluster FanControl commands
+    MTRCommandIDTypeClusterFanControlCommandStepID MTR_NEWLY_AVAILABLE = 0x00000000,
+
     // Cluster ColorControl deprecated command id names
     MTRClusterColorControlCommandMoveToHueID MTR_DEPRECATED("Please use MTRCommandIDTypeClusterColorControlCommandMoveToHueID",
         ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4))
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
index f5a0b6a..277d4d1 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
@@ -4761,6 +4761,11 @@
                                    queue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER
     API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params
+           expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries
+    expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs
+               completion:(MTRStatusCompletion)completion MTR_NEWLY_AVAILABLE;
+
 - (NSDictionary<NSString *, id> *)readAttributeFanModeWithParams:(MTRReadParams * _Nullable)params
     API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 - (void)writeAttributeFanModeWithValue:(NSDictionary<NSString *, id> *)dataValueDictionary
@@ -4836,6 +4841,13 @@
                                     params:(MTRWriteParams * _Nullable)params
     API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 
+- (NSDictionary<NSString *, id> *)readAttributeAirflowDirectionWithParams:(MTRReadParams * _Nullable)params MTR_NEWLY_AVAILABLE;
+- (void)writeAttributeAirflowDirectionWithValue:(NSDictionary<NSString *, id> *)dataValueDictionary
+                          expectedValueInterval:(NSNumber *)expectedValueIntervalMs MTR_NEWLY_AVAILABLE;
+- (void)writeAttributeAirflowDirectionWithValue:(NSDictionary<NSString *, id> *)dataValueDictionary
+                          expectedValueInterval:(NSNumber *)expectedValueIntervalMs
+                                         params:(MTRWriteParams * _Nullable)params MTR_NEWLY_AVAILABLE;
+
 - (NSDictionary<NSString *, id> *)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params
     API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1));
 
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
index c20c801..37861d9 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
@@ -21555,6 +21555,83 @@
     return self;
 }
 
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params
+           expectedValues:(NSArray<NSDictionary<NSString *, id> *> *)expectedValues
+    expectedValueInterval:(NSNumber *)expectedValueIntervalMs
+               completion:(MTRStatusCompletion)completion
+{
+    NSString * logPrefix =
+        [NSString stringWithFormat:@"MTRDevice command %u %u %u %u", self.device.deviceController.fabricIndex, _endpoint,
+                  (unsigned int) MTRClusterIDTypeFanControlID, (unsigned int) MTRCommandIDTypeClusterFanControlCommandStepID];
+    // Make a copy of params before we go async.
+    params = [params copy];
+    NSNumber * timedInvokeTimeoutMsParam = params.timedInvokeTimeoutMs;
+    if (timedInvokeTimeoutMsParam) {
+        timedInvokeTimeoutMsParam = MTRClampedNumber(timedInvokeTimeoutMsParam, @(1), @(UINT16_MAX));
+    }
+    MTRAsyncCallbackQueueWorkItem * workItem = [[MTRAsyncCallbackQueueWorkItem alloc] initWithQueue:self.device.queue];
+    MTRAsyncCallbackReadyHandler readyHandler = ^(MTRDevice * device, NSUInteger retryCount) {
+        MTRClustersLogDequeue(logPrefix, self.device.asyncCallbackWorkQueue);
+        MTRBaseDevice * baseDevice = [[MTRBaseDevice alloc] initWithNodeID:self.device.nodeID
+                                                                controller:self.device.deviceController];
+        auto * bridge = new MTRCommandSuccessCallbackBridge(
+            self.device.queue,
+            ^(id _Nullable value, NSError * _Nullable error) {
+                MTRClustersLogCompletion(logPrefix, value, error);
+                dispatch_async(self.callbackQueue, ^{
+                    completion(error);
+                });
+                [workItem endWork];
+            },
+            ^(ExchangeManager & exchangeManager, const SessionHandle & session, CommandSuccessCallbackType successCb,
+                MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
+                auto * typedBridge = static_cast<MTRCommandSuccessCallbackBridge *>(bridge);
+                Optional<uint16_t> timedInvokeTimeoutMs;
+                Optional<Timeout> invokeTimeout;
+                ListFreer listFreer;
+                FanControl::Commands::Step::Type request;
+                if (timedInvokeTimeoutMsParam != nil) {
+                    timedInvokeTimeoutMs.SetValue(timedInvokeTimeoutMsParam.unsignedShortValue);
+                }
+                if (params != nil) {
+                    if (params.serverSideProcessingTimeout != nil) {
+                        // Clamp to a number of seconds that will not overflow 32-bit
+                        // int when converted to ms.
+                        auto * serverSideProcessingTimeout
+                            = MTRClampedNumber(params.serverSideProcessingTimeout, @(0), @(UINT16_MAX));
+                        invokeTimeout.SetValue(Seconds16(serverSideProcessingTimeout.unsignedShortValue));
+                    }
+                }
+                request.direction
+                    = static_cast<std::remove_reference_t<decltype(request.direction)>>(params.direction.unsignedCharValue);
+                if (params.wrap != nil) {
+                    auto & definedValue_0 = request.wrap.Emplace();
+                    definedValue_0 = params.wrap.boolValue;
+                }
+                if (params.lowestOff != nil) {
+                    auto & definedValue_0 = request.lowestOff.Emplace();
+                    definedValue_0 = params.lowestOff.boolValue;
+                }
+
+                return MTRStartInvokeInteraction(typedBridge, request, exchangeManager, session, successCb, failureCb,
+                    self->_endpoint, timedInvokeTimeoutMs, invokeTimeout);
+            });
+        std::move(*bridge).DispatchAction(baseDevice);
+    };
+    workItem.readyHandler = readyHandler;
+    MTRClustersLogEnqueue(logPrefix, self.device.asyncCallbackWorkQueue);
+    [self.device.asyncCallbackWorkQueue enqueueWorkItem:workItem];
+
+    if (!expectedValueIntervalMs || ([expectedValueIntervalMs compare:@(0)] == NSOrderedAscending)) {
+        expectedValues = nil;
+    } else {
+        expectedValueIntervalMs = MTRClampedNumber(expectedValueIntervalMs, @(1), @(UINT32_MAX));
+    }
+    if (expectedValues) {
+        [self.device setExpectedValues:expectedValues expectedValueInterval:expectedValueIntervalMs];
+    }
+}
+
 - (NSDictionary<NSString *, id> *)readAttributeFanModeWithParams:(MTRReadParams * _Nullable)params
 {
     return [self.device readAttributeWithEndpointID:@(_endpoint)
@@ -21757,6 +21834,33 @@
                             timedWriteTimeout:timedWriteTimeout];
 }
 
+- (NSDictionary<NSString *, id> *)readAttributeAirflowDirectionWithParams:(MTRReadParams * _Nullable)params
+{
+    return [self.device readAttributeWithEndpointID:@(_endpoint)
+                                          clusterID:@(MTRClusterIDTypeFanControlID)
+                                        attributeID:@(MTRAttributeIDTypeClusterFanControlAttributeAirflowDirectionID)
+                                             params:params];
+}
+
+- (void)writeAttributeAirflowDirectionWithValue:(NSDictionary<NSString *, id> *)dataValueDictionary
+                          expectedValueInterval:(NSNumber *)expectedValueIntervalMs
+{
+    [self writeAttributeAirflowDirectionWithValue:dataValueDictionary expectedValueInterval:expectedValueIntervalMs params:nil];
+}
+- (void)writeAttributeAirflowDirectionWithValue:(NSDictionary<NSString *, id> *)dataValueDictionary
+                          expectedValueInterval:(NSNumber *)expectedValueIntervalMs
+                                         params:(MTRWriteParams * _Nullable)params
+{
+    NSNumber * timedWriteTimeout = params.timedWriteTimeout;
+
+    [self.device writeAttributeWithEndpointID:@(_endpoint)
+                                    clusterID:@(MTRClusterIDTypeFanControlID)
+                                  attributeID:@(MTRAttributeIDTypeClusterFanControlAttributeAirflowDirectionID)
+                                        value:dataValueDictionary
+                        expectedValueInterval:expectedValueIntervalMs
+                            timedWriteTimeout:timedWriteTimeout];
+}
+
 - (NSDictionary<NSString *, id> *)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params
 {
     return [self.device readAttributeWithEndpointID:@(_endpoint)
@@ -21814,6 +21918,16 @@
     return [self initWithDevice:device endpointID:@(endpoint) queue:queue];
 }
 
+- (void)stepWithParams:(MTRFanControlClusterStepParams *)params
+           expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries
+    expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs
+        completionHandler:(MTRStatusCompletion)completionHandler
+{
+    [self stepWithParams:params
+               expectedValues:expectedDataValueDictionaries
+        expectedValueInterval:expectedValueIntervalMs
+                   completion:completionHandler];
+}
 @end
 
 @implementation MTRClusterThermostatUserInterfaceConfiguration
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
index 23a469f..24c4e75 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
@@ -5995,6 +5995,40 @@
 @property (nonatomic, copy, nullable) NSNumber * serverSideProcessingTimeout;
 @end
 
+MTR_NEWLY_AVAILABLE
+@interface MTRFanControlClusterStepParams : NSObject <NSCopying>
+
+@property (nonatomic, copy) NSNumber * _Nonnull direction MTR_NEWLY_AVAILABLE;
+
+@property (nonatomic, copy) NSNumber * _Nullable wrap MTR_NEWLY_AVAILABLE;
+
+@property (nonatomic, copy) NSNumber * _Nullable lowestOff MTR_NEWLY_AVAILABLE;
+/**
+ * Controls whether the command is a timed command (using Timed Invoke).
+ *
+ * If nil (the default value), a regular invoke is done for commands that do
+ * not require a timed invoke and a timed invoke with some default timed request
+ * timeout is done for commands that require a timed invoke.
+ *
+ * If not nil, a timed invoke is done, with the provided value used as the timed
+ * request timeout.  The value should be chosen small enough to provide the
+ * desired security properties but large enough that it will allow a round-trip
+ * from the sever to the client (for the status response and actual invoke
+ * request) within the timeout window.
+ *
+ */
+@property (nonatomic, copy, nullable) NSNumber * timedInvokeTimeoutMs;
+
+/**
+ * Controls how much time, in seconds, we will allow for the server to process the command.
+ *
+ * The command will then time out if that much time, plus an allowance for retransmits due to network failures, passes.
+ *
+ * If nil, the framework will try to select an appropriate timeout value itself.
+ */
+@property (nonatomic, copy, nullable) NSNumber * serverSideProcessingTimeout;
+@end
+
 API_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
 @interface MTRColorControlClusterMoveToHueParams : NSObject <NSCopying>
 
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
index b3731db..54b2cca 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
@@ -9099,6 +9099,43 @@
 }
 
 @end
+@implementation MTRFanControlClusterStepParams
+- (instancetype)init
+{
+    if (self = [super init]) {
+
+        _direction = @(0);
+
+        _wrap = nil;
+
+        _lowestOff = nil;
+        _timedInvokeTimeoutMs = nil;
+        _serverSideProcessingTimeout = nil;
+    }
+    return self;
+}
+
+- (id)copyWithZone:(NSZone * _Nullable)zone;
+{
+    auto other = [[MTRFanControlClusterStepParams alloc] init];
+
+    other.direction = self.direction;
+    other.wrap = self.wrap;
+    other.lowestOff = self.lowestOff;
+    other.timedInvokeTimeoutMs = self.timedInvokeTimeoutMs;
+    other.serverSideProcessingTimeout = self.serverSideProcessingTimeout;
+
+    return other;
+}
+
+- (NSString *)description
+{
+    NSString * descriptionString = [NSString stringWithFormat:@"<%@: direction:%@; wrap:%@; lowestOff:%@; >",
+                                             NSStringFromClass([self class]), _direction, _wrap, _lowestOff];
+    return descriptionString;
+}
+
+@end
 @implementation MTRColorControlClusterMoveToHueParams
 - (instancetype)init
 {
diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
index b403e7b..b305704 100644
--- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
+++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
@@ -16609,6 +16609,37 @@
 
 } // namespace WindSetting
 
+namespace AirflowDirection {
+
+EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::FanControl::AirflowDirectionEnum * value)
+{
+    using Traits = NumericAttributeTraits<chip::app::Clusters::FanControl::AirflowDirectionEnum>;
+    Traits::StorageType temp;
+    uint8_t * readable   = Traits::ToAttributeStoreRepresentation(temp);
+    EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::FanControl::Id, Id, readable, sizeof(temp));
+    VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status);
+    if (!Traits::CanRepresentValue(/* isNullable = */ false, temp))
+    {
+        return EMBER_ZCL_STATUS_CONSTRAINT_ERROR;
+    }
+    *value = Traits::StorageToWorking(temp);
+    return status;
+}
+EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::FanControl::AirflowDirectionEnum value)
+{
+    using Traits = NumericAttributeTraits<chip::app::Clusters::FanControl::AirflowDirectionEnum>;
+    if (!Traits::CanRepresentValue(/* isNullable = */ false, value))
+    {
+        return EMBER_ZCL_STATUS_CONSTRAINT_ERROR;
+    }
+    Traits::StorageType storageValue;
+    Traits::WorkingToStorage(value, storageValue);
+    uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
+    return emberAfWriteAttribute(endpoint, Clusters::FanControl::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE);
+}
+
+} // namespace AirflowDirection
+
 namespace FeatureMap {
 
 EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value)
diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
index 94bf7b1..086820b 100644
--- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
+++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
@@ -2988,6 +2988,11 @@
 EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value);
 } // namespace WindSetting
 
+namespace AirflowDirection {
+EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::FanControl::AirflowDirectionEnum * value); // AirflowDirectionEnum
+EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::FanControl::AirflowDirectionEnum value);
+} // namespace AirflowDirection
+
 namespace FeatureMap {
 EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32
 EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value);
diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h
index abe7249..0c2f241 100644
--- a/zzz_generated/app-common/app-common/zap-generated/callback.h
+++ b/zzz_generated/app-common/app-common/zap-generated/callback.h
@@ -11482,6 +11482,12 @@
     chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
     const chip::app::Clusters::Thermostat::Commands::ClearWeeklySchedule::DecodableType & commandData);
 /**
+ * @brief Fan Control Cluster Step Command callback (from client)
+ */
+bool emberAfFanControlClusterStepCallback(chip::app::CommandHandler * commandObj,
+                                          const chip::app::ConcreteCommandPath & commandPath,
+                                          const chip::app::Clusters::FanControl::Commands::Step::DecodableType & commandData);
+/**
  * @brief Color Control Cluster MoveToHue Command callback (from client)
  */
 bool emberAfColorControlClusterMoveToHueCallback(
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
index 07c3a13..5520652 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums-check.h
@@ -2165,6 +2165,30 @@
     }
 }
 
+static auto __attribute__((unused)) EnsureKnownEnumValue(FanControl::AirflowDirectionEnum val)
+{
+    using EnumType = FanControl::AirflowDirectionEnum;
+    switch (val)
+    {
+    case EnumType::kForward:
+    case EnumType::kReverse:
+        return val;
+    default:
+        return static_cast<EnumType>(2);
+    }
+}
+static auto __attribute__((unused)) EnsureKnownEnumValue(FanControl::DirectionEnum val)
+{
+    using EnumType = FanControl::DirectionEnum;
+    switch (val)
+    {
+    case EnumType::kIncrease:
+    case EnumType::kDecrease:
+        return val;
+    default:
+        return static_cast<EnumType>(2);
+    }
+}
 static auto __attribute__((unused)) EnsureKnownEnumValue(FanControl::FanModeSequenceType val)
 {
     using EnumType = FanControl::FanModeSequenceType;
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
index 8b37509..47bec8a 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-enums.h
@@ -2849,6 +2849,30 @@
 
 namespace FanControl {
 
+// Enum for AirflowDirectionEnum
+enum class AirflowDirectionEnum : uint8_t
+{
+    kForward = 0x00,
+    kReverse = 0x01,
+    // All received enum values that are not listed above will be mapped
+    // to kUnknownEnumValue. This is a helper enum value that should only
+    // be used by code to process how it handles receiving and unknown
+    // enum value. This specific should never be transmitted.
+    kUnknownEnumValue = 2,
+};
+
+// Enum for DirectionEnum
+enum class DirectionEnum : uint8_t
+{
+    kIncrease = 0x00,
+    kDecrease = 0x01,
+    // All received enum values that are not listed above will be mapped
+    // to kUnknownEnumValue. This is a helper enum value that should only
+    // be used by code to process how it handles receiving and unknown
+    // enum value. This specific should never be transmitted.
+    kUnknownEnumValue = 2,
+};
+
 // Enum for FanModeSequenceType
 enum class FanModeSequenceType : uint8_t
 {
@@ -2885,10 +2909,12 @@
 // Bitmap for Feature
 enum class Feature : uint32_t
 {
-    kMultiSpeed = 0x1,
-    kAuto       = 0x2,
-    kRocking    = 0x4,
-    kWind       = 0x8,
+    kMultiSpeed       = 0x1,
+    kAuto             = 0x2,
+    kRocking          = 0x4,
+    kWind             = 0x8,
+    kStep             = 0x10,
+    kAirflowDirection = 0x20,
 };
 
 // Bitmap for RockSupportMask
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
index eb2f49d..67f09f8 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
@@ -16854,7 +16854,53 @@
 } // namespace Thermostat
 namespace FanControl {
 
-namespace Commands {} // namespace Commands
+namespace Commands {
+namespace Step {
+CHIP_ERROR Type::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
+{
+    TLV::TLVType outer;
+    ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer));
+    ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(Fields::kDirection), direction));
+    ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(Fields::kWrap), wrap));
+    ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(Fields::kLowestOff), lowestOff));
+    ReturnErrorOnFailure(writer.EndContainer(outer));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    TLV::TLVType outer;
+    VerifyOrReturnError(TLV::kTLVType_Structure == reader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE);
+    ReturnErrorOnFailure(reader.EnterContainer(outer));
+    while ((err = reader.Next()) == CHIP_NO_ERROR)
+    {
+        if (!TLV::IsContextTag(reader.GetTag()))
+        {
+            continue;
+        }
+        switch (TLV::TagNumFromTag(reader.GetTag()))
+        {
+        case to_underlying(Fields::kDirection):
+            ReturnErrorOnFailure(DataModel::Decode(reader, direction));
+            break;
+        case to_underlying(Fields::kWrap):
+            ReturnErrorOnFailure(DataModel::Decode(reader, wrap));
+            break;
+        case to_underlying(Fields::kLowestOff):
+            ReturnErrorOnFailure(DataModel::Decode(reader, lowestOff));
+            break;
+        default:
+            break;
+        }
+    }
+
+    VerifyOrReturnError(err == CHIP_END_OF_TLV, err);
+    ReturnErrorOnFailure(reader.ExitContainer(outer));
+    return CHIP_NO_ERROR;
+}
+} // namespace Step.
+} // namespace Commands
 
 namespace Attributes {
 CHIP_ERROR TypeInfo::DecodableType::Decode(TLV::TLVReader & reader, const ConcreteAttributePath & path)
@@ -16894,6 +16940,9 @@
     case Attributes::WindSetting::TypeInfo::GetAttributeId():
         ReturnErrorOnFailure(DataModel::Decode(reader, windSetting));
         break;
+    case Attributes::AirflowDirection::TypeInfo::GetAttributeId():
+        ReturnErrorOnFailure(DataModel::Decode(reader, airflowDirection));
+        break;
     case Attributes::GeneratedCommandList::TypeInfo::GetAttributeId():
         ReturnErrorOnFailure(DataModel::Decode(reader, generatedCommandList));
         break;
@@ -27229,6 +27278,13 @@
             return false;
         }
     }
+    case Clusters::FanControl::Id: {
+        switch (aCommand)
+        {
+        default:
+            return false;
+        }
+    }
     case Clusters::ColorControl::Id: {
         switch (aCommand)
         {
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
index d14b390..8b8c7fb 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
@@ -22138,6 +22138,57 @@
 } // namespace Thermostat
 namespace FanControl {
 
+namespace Commands {
+// Forward-declarations so we can reference these later.
+
+namespace Step {
+struct Type;
+struct DecodableType;
+} // namespace Step
+
+} // namespace Commands
+
+namespace Commands {
+namespace Step {
+enum class Fields : uint8_t
+{
+    kDirection = 0,
+    kWrap      = 1,
+    kLowestOff = 2,
+};
+
+struct Type
+{
+public:
+    // Use GetCommandId instead of commandId directly to avoid naming conflict with CommandIdentification in ExecutionOfACommand
+    static constexpr CommandId GetCommandId() { return Commands::Step::Id; }
+    static constexpr ClusterId GetClusterId() { return Clusters::FanControl::Id; }
+
+    DirectionEnum direction = static_cast<DirectionEnum>(0);
+    Optional<bool> wrap;
+    Optional<bool> lowestOff;
+
+    CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const;
+
+    using ResponseType = DataModel::NullObjectType;
+
+    static constexpr bool MustUseTimedInvoke() { return false; }
+};
+
+struct DecodableType
+{
+public:
+    static constexpr CommandId GetCommandId() { return Commands::Step::Id; }
+    static constexpr ClusterId GetClusterId() { return Clusters::FanControl::Id; }
+
+    DirectionEnum direction = static_cast<DirectionEnum>(0);
+    Optional<bool> wrap;
+    Optional<bool> lowestOff;
+    CHIP_ERROR Decode(TLV::TLVReader & reader);
+};
+}; // namespace Step
+} // namespace Commands
+
 namespace Attributes {
 
 namespace FanMode {
@@ -22272,6 +22323,18 @@
     static constexpr bool MustUseTimedWrite() { return false; }
 };
 } // namespace WindSetting
+namespace AirflowDirection {
+struct TypeInfo
+{
+    using Type             = chip::app::Clusters::FanControl::AirflowDirectionEnum;
+    using DecodableType    = chip::app::Clusters::FanControl::AirflowDirectionEnum;
+    using DecodableArgType = chip::app::Clusters::FanControl::AirflowDirectionEnum;
+
+    static constexpr ClusterId GetClusterId() { return Clusters::FanControl::Id; }
+    static constexpr AttributeId GetAttributeId() { return Attributes::AirflowDirection::Id; }
+    static constexpr bool MustUseTimedWrite() { return false; }
+};
+} // namespace AirflowDirection
 namespace GeneratedCommandList {
 struct TypeInfo : public Clusters::Globals::Attributes::GeneratedCommandList::TypeInfo
 {
@@ -22329,6 +22392,8 @@
         Attributes::RockSetting::TypeInfo::DecodableType rockSetting   = static_cast<uint8_t>(0);
         Attributes::WindSupport::TypeInfo::DecodableType windSupport   = static_cast<uint8_t>(0);
         Attributes::WindSetting::TypeInfo::DecodableType windSetting   = static_cast<uint8_t>(0);
+        Attributes::AirflowDirection::TypeInfo::DecodableType airflowDirection =
+            static_cast<chip::app::Clusters::FanControl::AirflowDirectionEnum>(0);
         Attributes::GeneratedCommandList::TypeInfo::DecodableType generatedCommandList;
         Attributes::AcceptedCommandList::TypeInfo::DecodableType acceptedCommandList;
         Attributes::EventList::TypeInfo::DecodableType eventList;
diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h
index 88b4d87..98137f4 100644
--- a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h
+++ b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h
@@ -4038,6 +4038,10 @@
 static constexpr AttributeId Id = 0x0000000A;
 } // namespace WindSetting
 
+namespace AirflowDirection {
+static constexpr AttributeId Id = 0x0000000B;
+} // namespace AirflowDirection
+
 namespace GeneratedCommandList {
 static constexpr AttributeId Id = Globals::Attributes::GeneratedCommandList::Id;
 } // namespace GeneratedCommandList
diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Commands.h b/zzz_generated/app-common/app-common/zap-generated/ids/Commands.h
index 566f589..f164933 100644
--- a/zzz_generated/app-common/app-common/zap-generated/ids/Commands.h
+++ b/zzz_generated/app-common/app-common/zap-generated/ids/Commands.h
@@ -997,6 +997,16 @@
 } // namespace Commands
 } // namespace Thermostat
 
+namespace FanControl {
+namespace Commands {
+
+namespace Step {
+static constexpr CommandId Id = 0x00000000;
+} // namespace Step
+
+} // namespace Commands
+} // namespace FanControl
+
 namespace ColorControl {
 namespace Commands {
 
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
index e0387eb..01b4df4 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
@@ -6324,6 +6324,7 @@
 | Cluster FanControl                                                  | 0x0202 |
 |------------------------------------------------------------------------------|
 | Commands:                                                           |        |
+| * Step                                                              |   0x00 |
 |------------------------------------------------------------------------------|
 | Attributes:                                                         |        |
 | * FanMode                                                           | 0x0000 |
@@ -6337,6 +6338,7 @@
 | * RockSetting                                                       | 0x0008 |
 | * WindSupport                                                       | 0x0009 |
 | * WindSetting                                                       | 0x000A |
+| * AirflowDirection                                                  | 0x000B |
 | * GeneratedCommandList                                              | 0xFFF8 |
 | * AcceptedCommandList                                               | 0xFFF9 |
 | * EventList                                                         | 0xFFFA |
@@ -6347,6 +6349,38 @@
 | Events:                                                             |        |
 \*----------------------------------------------------------------------------*/
 
+/*
+ * Command Step
+ */
+class FanControlStep : public ClusterCommand
+{
+public:
+    FanControlStep(CredentialIssuerCommands * credsIssuerConfig) : ClusterCommand("step", credsIssuerConfig)
+    {
+        AddArgument("Direction", 0, UINT8_MAX, &mRequest.direction);
+        AddArgument("Wrap", 0, 1, &mRequest.wrap);
+        AddArgument("LowestOff", 0, 1, &mRequest.lowestOff);
+        ClusterCommand::AddArguments();
+    }
+
+    CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector<chip::EndpointId> endpointIds) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) command (0x00000000) on endpoint %u", endpointIds.at(0));
+
+        return ClusterCommand::SendCommand(device, endpointIds.at(0), 0x00000202, 0x00000000, mRequest);
+    }
+
+    CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) command (0x00000000) on Group %u", groupId);
+
+        return ClusterCommand::SendGroupCommand(groupId, fabricIndex, 0x00000202, 0x00000000, mRequest);
+    }
+
+private:
+    chip::app::Clusters::FanControl::Commands::Step::Type mRequest;
+};
+
 /*----------------------------------------------------------------------------*\
 | Cluster ThermostatUserInterfaceConfiguration                        | 0x0204 |
 |------------------------------------------------------------------------------|
@@ -16514,6 +16548,7 @@
         // Commands
         //
         make_unique<ClusterCommand>(Id, credsIssuerConfig), //
+        make_unique<FanControlStep>(credsIssuerConfig),     //
         //
         // Attributes
         //
@@ -16529,6 +16564,7 @@
         make_unique<ReadAttribute>(Id, "rock-setting", Attributes::RockSetting::Id, credsIssuerConfig),                    //
         make_unique<ReadAttribute>(Id, "wind-support", Attributes::WindSupport::Id, credsIssuerConfig),                    //
         make_unique<ReadAttribute>(Id, "wind-setting", Attributes::WindSetting::Id, credsIssuerConfig),                    //
+        make_unique<ReadAttribute>(Id, "airflow-direction", Attributes::AirflowDirection::Id, credsIssuerConfig),          //
         make_unique<ReadAttribute>(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), //
         make_unique<ReadAttribute>(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig),   //
         make_unique<ReadAttribute>(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig),                        //
@@ -16558,6 +16594,9 @@
                                              WriteCommandType::kForceWrite, credsIssuerConfig), //
         make_unique<WriteAttribute<uint8_t>>(Id, "wind-setting", 0, UINT8_MAX, Attributes::WindSetting::Id,
                                              WriteCommandType::kWrite, credsIssuerConfig), //
+        make_unique<WriteAttribute<chip::app::Clusters::FanControl::AirflowDirectionEnum>>(
+            Id, "airflow-direction", 0, UINT8_MAX, Attributes::AirflowDirection::Id, WriteCommandType::kWrite,
+            credsIssuerConfig), //
         make_unique<WriteAttributeAsComplex<chip::app::DataModel::List<const chip::CommandId>>>(
             Id, "generated-command-list", Attributes::GeneratedCommandList::Id, WriteCommandType::kForceWrite,
             credsIssuerConfig), //
@@ -16583,6 +16622,7 @@
         make_unique<SubscribeAttribute>(Id, "rock-setting", Attributes::RockSetting::Id, credsIssuerConfig),                    //
         make_unique<SubscribeAttribute>(Id, "wind-support", Attributes::WindSupport::Id, credsIssuerConfig),                    //
         make_unique<SubscribeAttribute>(Id, "wind-setting", Attributes::WindSetting::Id, credsIssuerConfig),                    //
+        make_unique<SubscribeAttribute>(Id, "airflow-direction", Attributes::AirflowDirection::Id, credsIssuerConfig),          //
         make_unique<SubscribeAttribute>(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), //
         make_unique<SubscribeAttribute>(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig),   //
         make_unique<SubscribeAttribute>(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig),                        //
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
index a4d2745..dcb545d 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
+++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
@@ -9744,6 +9744,11 @@
             ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value));
             return DataModelLogger::LogValue("WindSetting", 1, value);
         }
+        case FanControl::Attributes::AirflowDirection::Id: {
+            chip::app::Clusters::FanControl::AirflowDirectionEnum value;
+            ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value));
+            return DataModelLogger::LogValue("AirflowDirection", 1, value);
+        }
         case FanControl::Attributes::GeneratedCommandList::Id: {
             chip::app::DataModel::DecodableList<chip::CommandId> value;
             ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value));
diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
index 65f4b62..5840ca5 100644
--- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
@@ -70204,6 +70204,7 @@
 | Cluster FanControl                                                  | 0x0202 |
 |------------------------------------------------------------------------------|
 | Commands:                                                           |        |
+| * Step                                                              |   0x00 |
 |------------------------------------------------------------------------------|
 | Attributes:                                                         |        |
 | * FanMode                                                           | 0x0000 |
@@ -70217,6 +70218,7 @@
 | * RockSetting                                                       | 0x0008 |
 | * WindSupport                                                       | 0x0009 |
 | * WindSetting                                                       | 0x000A |
+| * AirflowDirection                                                  | 0x000B |
 | * GeneratedCommandList                                              | 0xFFF8 |
 | * AcceptedCommandList                                               | 0xFFF9 |
 | * EventList                                                         | 0xFFFA |
@@ -70228,6 +70230,64 @@
 \*----------------------------------------------------------------------------*/
 
 /*
+ * Command Step
+ */
+class FanControlStep : public ClusterCommand {
+public:
+    FanControlStep()
+        : ClusterCommand("step")
+    {
+        AddArgument("Direction", 0, UINT8_MAX, &mRequest.direction);
+        AddArgument("Wrap", 0, 1, &mRequest.wrap);
+        AddArgument("LowestOff", 0, 1, &mRequest.lowestOff);
+        ClusterCommand::AddArguments();
+    }
+
+    CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) command (0x00000000) on endpoint %u", endpointId);
+
+        dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL);
+        __auto_type * cluster = [[MTRBaseClusterFanControl alloc] initWithDevice:device
+                                                                      endpointID:@(endpointId)
+                                                                           queue:callbackQueue];
+        __auto_type * params = [[MTRFanControlClusterStepParams alloc] init];
+        params.timedInvokeTimeoutMs
+            = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
+        params.direction = [NSNumber numberWithUnsignedChar:chip::to_underlying(mRequest.direction)];
+        if (mRequest.wrap.HasValue()) {
+            params.wrap = [NSNumber numberWithBool:mRequest.wrap.Value()];
+        } else {
+            params.wrap = nil;
+        }
+        if (mRequest.lowestOff.HasValue()) {
+            params.lowestOff = [NSNumber numberWithBool:mRequest.lowestOff.Value()];
+        } else {
+            params.lowestOff = nil;
+        }
+        uint16_t repeatCount = mRepeatCount.ValueOr(1);
+        uint16_t __block responsesNeeded = repeatCount;
+        while (repeatCount--) {
+            [cluster stepWithParams:params
+                         completion:^(NSError * _Nullable error) {
+                             responsesNeeded--;
+                             if (error != nil) {
+                                 mError = error;
+                                 LogNSError("Error", error);
+                             }
+                             if (responsesNeeded == 0) {
+                                 SetCommandExitStatus(mError);
+                             }
+                         }];
+        }
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::app::Clusters::FanControl::Commands::Step::Type mRequest;
+};
+
+/*
  * Attribute FanMode
  */
 class ReadFanControlFanMode : public ReadAttribute {
@@ -71238,6 +71298,116 @@
 };
 
 /*
+ * Attribute AirflowDirection
+ */
+class ReadFanControlAirflowDirection : public ReadAttribute {
+public:
+    ReadFanControlAirflowDirection()
+        : ReadAttribute("airflow-direction")
+    {
+    }
+
+    ~ReadFanControlAirflowDirection() {}
+
+    CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) ReadAttribute (0x0000000B) on endpoint %u", endpointId);
+
+        dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL);
+        __auto_type * cluster = [[MTRBaseClusterFanControl alloc] initWithDevice:device
+                                                                      endpointID:@(endpointId)
+                                                                           queue:callbackQueue];
+        [cluster readAttributeAirflowDirectionWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) {
+            NSLog(@"FanControl.AirflowDirection response %@", [value description]);
+            if (error != nil) {
+                LogNSError("FanControl AirflowDirection read Error", error);
+            }
+            SetCommandExitStatus(error);
+        }];
+        return CHIP_NO_ERROR;
+    }
+};
+
+class WriteFanControlAirflowDirection : public WriteAttribute {
+public:
+    WriteFanControlAirflowDirection()
+        : WriteAttribute("airflow-direction")
+    {
+        AddArgument("attr-name", "airflow-direction");
+        AddArgument("attr-value", 0, UINT8_MAX, &mValue);
+        WriteAttribute::AddArguments();
+    }
+
+    ~WriteFanControlAirflowDirection() {}
+
+    CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) WriteAttribute (0x0000000B) on endpoint %u", endpointId);
+        dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL);
+        __auto_type * cluster = [[MTRBaseClusterFanControl alloc] initWithDevice:device
+                                                                      endpointID:@(endpointId)
+                                                                           queue:callbackQueue];
+        __auto_type * params = [[MTRWriteParams alloc] init];
+        params.timedWriteTimeout
+            = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
+        params.dataVersion = mDataVersion.HasValue() ? [NSNumber numberWithUnsignedInt:mDataVersion.Value()] : nil;
+        NSNumber * _Nonnull value = [NSNumber numberWithUnsignedChar:mValue];
+
+        [cluster writeAttributeAirflowDirectionWithValue:value
+                                                  params:params
+                                              completion:^(NSError * _Nullable error) {
+                                                  if (error != nil) {
+                                                      LogNSError("FanControl AirflowDirection write Error", error);
+                                                  }
+                                                  SetCommandExitStatus(error);
+                                              }];
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    uint8_t mValue;
+};
+
+class SubscribeAttributeFanControlAirflowDirection : public SubscribeAttribute {
+public:
+    SubscribeAttributeFanControlAirflowDirection()
+        : SubscribeAttribute("airflow-direction")
+    {
+    }
+
+    ~SubscribeAttributeFanControlAirflowDirection() {}
+
+    CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override
+    {
+        ChipLogProgress(chipTool, "Sending cluster (0x00000202) ReportAttribute (0x0000000B) on endpoint %u", endpointId);
+        dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL);
+        __auto_type * cluster = [[MTRBaseClusterFanControl alloc] initWithDevice:device
+                                                                      endpointID:@(endpointId)
+                                                                           queue:callbackQueue];
+        __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(mMinInterval) maxInterval:@(mMaxInterval)];
+        if (mKeepSubscriptions.HasValue()) {
+            params.replaceExistingSubscriptions = !mKeepSubscriptions.Value();
+        }
+        if (mFabricFiltered.HasValue()) {
+            params.filterByFabric = mFabricFiltered.Value();
+        }
+        if (mAutoResubscribe.HasValue()) {
+            params.resubscribeAutomatically = mAutoResubscribe.Value();
+        }
+        [cluster subscribeAttributeAirflowDirectionWithParams:params
+            subscriptionEstablished:^() {
+                mSubscriptionEstablished = YES;
+            }
+            reportHandler:^(NSNumber * _Nullable value, NSError * _Nullable error) {
+                NSLog(@"FanControl.AirflowDirection response %@", [value description]);
+                SetCommandExitStatus(error);
+            }];
+
+        return CHIP_NO_ERROR;
+    }
+};
+
+/*
  * Attribute GeneratedCommandList
  */
 class ReadFanControlGeneratedCommandList : public ReadAttribute {
@@ -162828,6 +162998,7 @@
 
     commands_list clusterCommands = {
         make_unique<ClusterCommand>(Id), //
+        make_unique<FanControlStep>(), //
         make_unique<ReadAttribute>(Id), //
         make_unique<ReadFanControlFanMode>(), //
         make_unique<WriteAttribute>(Id), //
@@ -162859,6 +163030,9 @@
         make_unique<ReadFanControlWindSetting>(), //
         make_unique<WriteFanControlWindSetting>(), //
         make_unique<SubscribeAttributeFanControlWindSetting>(), //
+        make_unique<ReadFanControlAirflowDirection>(), //
+        make_unique<WriteFanControlAirflowDirection>(), //
+        make_unique<SubscribeAttributeFanControlAirflowDirection>(), //
         make_unique<ReadFanControlGeneratedCommandList>(), //
         make_unique<SubscribeAttributeFanControlGeneratedCommandList>(), //
         make_unique<ReadFanControlAcceptedCommandList>(), //