Matter custom cluster compile failed in NCS V3.1.1

Hello, Nordic Team!

I am developing another emergency project. And we using the ncs v3.1.1 and NRF54L15. But there is a compile problem about the custom cluster.

Here is my xml file.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<configurator>

<struct name="ValveFreezeStateStruct">
    <cluster code="0xFFF1FC01"/>
    <item type="boolean" name="freezed"/>
    <item type="int64u" name="timestamp"/>
</struct>

<struct name="ValveTemperatureStateStruct">
    <cluster code="0xFFF1FC01"/>
    <item type="int16u" name="temperature"/>
    <item type="int64u" name="timestamp"/>
</struct>

<struct name="ValveSessionLogStruct">
    <cluster code="0xFFF1FC01"/>
    <item type="int64u" name="Begin"/>
    <item type="int64u" name="End"/>
    <item type="int16u" name="Volume"/>
</struct>

<struct name="ValveSessionStruct">
    <cluster code="0xFFF1FC01"/>
    <item name="Weekdays"  type="int8u"/>
    <item name="Hour"      type="int8u"/>
    <item name="Minute"    type="int8u"/>
    <item name="Volume"    type="int16u"/>
    <item name="Duration"  type="int32u"/>
</struct>

<enum name="ValveSessionOperationEnum" type="int8u">
    <cluster code="0xFFF1FC01"/>
<!-- create a new valve session with status-->
    <item name="Disable"   value="0"/>
    <item name="Enable"    value="1"/>
    <item name="Remove"    value="2"/>
</enum>

<enum name="ValveSessionStateEnum" type="int8u">
    <cluster code="0xFFF1FC01"/>
    <item name="Disable"   value="0"/>
    <item name="Enable"    value="1"/>
    <item name="Running"   value="2"/>
</enum>

<struct name="ValveSessionStateStruct">
    <cluster code="0xFFF1FC01"/>
    <item name="Session"  type="ValveSessionStruct"/>
    <item name="State"    type="ValveSessionStateEnum"/>
</struct>

<enum name="WaterStatsEnum" type="int8u">
    <cluster code="0xFFF1FC01"/>
    <item name="Hour"       value="0"/>
    <item name="Day"        value="1"/>
    <item name="Week"       value="2"/>
    <item name="PastWeek"   value="3"/>
    <item name="Month"      value="4"/>
    <item name="Year"       value="5"/>
</enum>

<struct name="WaterUsageStruct">
    <cluster code="0xFFF1FC01"/>
    <item name="Hour"         type="int32u"/>
    <item name="Today"        type="int32u"/>
    <item name="Yesterday"    type="int32u"/>
    <item name="CurrentWeek"  type="int32u"/>
    <item name="LastWeek"     type="int32u"/>
    <item name="CurrentMonth" type="int32u"/>
    <item name="LastMonth"    type="int32u"/>
    <item name="CurrentYear"  type="int32u"/>
    <item name="LastYear"     type="int32u"/>
</struct>

<cluster>
    <domain>General</domain>
    <name>WaterValveData</name>
    <code>0xFFF1FC01</code>
    <define>WATER_VALVE_DATA_CLUSTER</define>
    <description>cluster for controlling and receiving data of water valves</description>

    <attribute side="server" code="0xFFF10000" define="VOLUME" type="int16u" writable="true" optional="false" default="0x00">Volume</attribute>
    <attribute side="server" code="0xFFF10001" define="VOLUME_REMAINING" type="int16u" writable="true" optional="false" default="0x00">VolumeRemaining</attribute>
    <attribute side="server" code="0xFFF10002" define="AQUAVALUE" type="int16u" writable="true" optional="false" default="0x00">AquaValue</attribute>
    <attribute side="server" code="0xFFF10003" define="CHILD_LOCK" type="boolean" writable="true" optional="false" default="false">ChildLock</attribute>
    <attribute side="server" code="0xFFF10004" define="TEMPERATURE_ADJUST" type="int8s" writable="true" optional="false" default="0x00">TemperatureAdjust</attribute>
    <attribute side="server" code="0xFFF10005" define="FREEZE_THRESHOLD" type="int8s" writable="true" optional="false" default="0x00">FreezeThreshold</attribute>

    <command source="client" code="0xFFF10000" name="GetValveSessionLogs" response="GetValveSessionLogsResponse">
      <description>get the valve session logs</description>
      <!-- One page contains 50 entries -->
      <arg name="page" type="int8u" array="false"/>
    </command>

    <command source="server" code="0xFFF10001" name="GetValveSessionLogsResponse">
      <description>response with valve session logs</description>
      <arg name="total" type="int16u" array="false"/>
      <arg name="logs" type="ValveSessionLogStruct" array="true"/>
    </command>

    <command source="client" code="0xFFF10002" name="SetValveSession" response="SetValveSessionResponse">
      <description>set the valve timer session</description>
      <arg name="session" type="ValveSessionStruct" array="false"/>
      <arg name="operation" type="ValveSessionOperationEnum" array="false"/>
    </command>

    <command source="server" code="0xFFF10003" name="SetValveSessionResponse">
      <description>return all the timer sessions</description>
      <arg name="total" type="int8u" array="false"/>
      <arg name="sessions" type="ValveSessionStateStruct" array="true"/>
    </command>

    <command source="client" code="0xFFF10004" name="GetAllValveSession" response="GetAllValveSessionResponse">
      <description>get the valve session logs</description>
    </command>

    <command source="server" code="0xFFF10005" name="GetAllValveSessionResponse">
      <description>response with all valve session logs</description>
      <arg name="total" type="int8u" array="false"/>
      <arg name="sessions" type="ValveSessionStateStruct" array="true"/>
    </command>

    <command source="client" code="0xFFF10006" name="GetWaterUsage" response="GetWaterUsageResponse">
      <description>get the water usage</description>
    </command>

    <command source="server" code="0xFFF10007" name="GetWaterUsageResponse">
      <description>response with water usage response</description>
      <arg name="usage" type="WaterUsageStruct" array="false"/>
    </command>

    <command source="client" code="0xFFF10008" name="GetWaterStats" response="GetWaterStatsResponse">
      <description>get the water stats</description>
      <arg name="last"  type="boolean" array="false"/>
      <arg name="stats" type="WaterStatsEnum" array="false"/>
    </command>

    <command source="server" code="0xFFF10009" name="GetWaterStatsResponse">
      <description>response with water stats response</description>
      <arg name="total" type="int32u" array="false"/>
      <arg name="stats" type="int32u" array="true"/>
    </command>

    <command source="client" code="0xFFF1000A" name="GetValveFreezeRecord" response="GetValveFreezeRecordResponse">
      <description>get the valve freeze record</description>
      <arg name="page" type="int8u" array="false"/>
    </command>

    <command source="server" code="0xFFF1000B" name="GetValveFreezeRecordResponse">
      <description>response with valve freeze record</description>
      <arg name="total" type="int32u" array="false"/>
      <arg name="freeze" type="ValveFreezeStateStruct" array="true"/>
    </command>

    <command source="client" code="0xFFF1000C" name="GetValveTemperatureRecord" response="GetValveTemperatureRecordResponse">
      <description>get the valve temperature record</description>
      <arg name="page" type="int8u" array="false"/>
    </command>

    <command source="server" code="0xFFF1000D" name="GetValveTemperatureRecordResponse">
      <description>response with valve temperature record</description>
      <arg name="total" type="int32u" array="false"/>
      <arg name="temperature" type="ValveTemperatureStateStruct" array="true"/>
    </command>

    <command source="client" code="0xFFF1000E" name="OpenValveWithVolume">
      <description>open the valve with voluem</description>
      <arg name="volume"  type="int16u" array="false"/>
    </command>

</cluster>

<deviceType>
    <name>water-valve-data-device</name>
    <domain>CHIP</domain>
    <typeName>Water Valve Data Device</typeName>
    <profileId editable="false">0x0FFF</profileId>
    <deviceId  editable="false">0xFFF10001</deviceId>
    <class>Simple</class>
    <scope>Endpoint</scope>
    <clusters lockOthers="true">
      <include cluster="WaterValveData"  client="true"  server="true"  clientLocked="false"  serverLocked="false"/>
    </clusters>
</deviceType>

</configurator>

And I copy the zcl.json to my project and place it to src/default_zap/. And if I don't execute the zap-append and just execute the  

west zap-gui -j src/default_zap/zcl.json --clusters src/default_zap/WaterValveData.xml

And we will see the error like:

champon@NC ~/D/P/GV1 (DeviceNoResponse) [250]> west zap-gui -j src/default_zap/zcl.json --clusters src/default_zap/WaterValveData.xml
Using ZAP file: /home/champon/Develop/Products/GV1/src/default_zap/GV1.zap
Using ZCL file: /home/champon/Develop/Products/GV1/src/default_zap/zcl.json
Using app templates: /home/champon/ncs/v3.1.1/modules/lib/matter/src/app/zap-templates/app-templates.json
ZAP installation directory: /home/champon/ncs/v3.1.1/modules/lib/matter/.zap-install
Found ZAP 2025.5.14 (up to date)
🔧 Using temporary state directory: /tmp/zap.Ea9wjH
[385668:1009/150431.617593:ERROR:bus.cc(407)] Failed to connect to the bus: '=' character not found or has no value following it
[385668:1009/150431.617645:ERROR:bus.cc(407)] Failed to connect to the bus: '=' character not found or has no value following it
[385668:1009/150431.617666:ERROR:bus.cc(407)] Failed to connect to the bus: '=' character not found or has no value following it
[385668:1009/150431.617676:ERROR:bus.cc(407)] Failed to connect to the bus: '=' character not found or has no value following it
Error while running sql statement: 
  INSERT INTO
    STRUCT_ITEM (STRUCT_REF, NAME, FIELD_IDENTIFIER, IS_ARRAY, IS_ENUM, MIN_LENGTH, MAX_LENGTH, DEFAULT_VALUE, IS_WRITABLE, IS_NULLABLE, IS_OPTIONAL, IS_FABRIC_SENSITIVE, SIZE, DATA_TYPE_REF)
  VALUES (
    (SELECT
      CASE
        WHEN
          (
  SELECT
       STRUCT_ID
     FROM
       STRUCT
     INNER JOIN
       DATA_TYPE ON STRUCT.STRUCT_ID = DATA_TYPE.DATA_TYPE_ID
     INNER JOIN
       DATA_TYPE_CLUSTER
     ON
       DATA_TYPE.DATA_TYPE_ID = DATA_TYPE_CLUSTER.DATA_TYPE_REF
     WHERE
       DATA_TYPE.PACKAGE_REF
     IN
       (2)
     AND
       DATA_TYPE.NAME = ?
     AND
       DATA_TYPE.DISCRIMINATOR_REF = (SELECT
                                        DISCRIMINATOR_ID
                                      FROM
                                        DISCRIMINATOR
                                      WHERE
                                        NAME = "STRUCT"
                                      AND
                                        PACKAGE_REF
                                      IN
                                        (2))
     AND
       DATA_TYPE_CLUSTER.CLUSTER_CODE = ?
  )
        IS
          NULL
        THEN
          (
  SELECT
       STRUCT_ID
     FROM
       STRUCT
     INNER JOIN
       DATA_TYPE ON STRUCT.STRUCT_ID = DATA_TYPE.DATA_TYPE_ID
     LEFT JOIN
       DATA_TYPE_CLUSTER ON DATA_TYPE.DATA_TYPE_ID = DATA_TYPE_CLUSTER.DATA_TYPE_REF
     WHERE
       DATA_TYPE.PACKAGE_REF
     IN
       (2)
     AND
       DATA_TYPE.NAME = ?
     AND
       DATA_TYPE.DISCRIMINATOR_REF = (SELECT
                                        DISCRIMINATOR_ID
                                      FROM
                                        DISCRIMINATOR
                                      WHERE
                                        NAME = "STRUCT"
                                      AND
                                        PACKAGE_REF
                                      IN
                                        (2))
     AND
       DATA_TYPE_CLUSTER.CLUSTER_CODE IS NULL
  )
        ELSE
          (
  SELECT
       STRUCT_ID
     FROM
       STRUCT
     INNER JOIN
       DATA_TYPE ON STRUCT.STRUCT_ID = DATA_TYPE.DATA_TYPE_ID
     INNER JOIN
       DATA_TYPE_CLUSTER
     ON
       DATA_TYPE.DATA_TYPE_ID = DATA_TYPE_CLUSTER.DATA_TYPE_REF
     WHERE
       DATA_TYPE.PACKAGE_REF
     IN
       (2)
     AND
       DATA_TYPE.NAME = ?
     AND
       DATA_TYPE.DISCRIMINATOR_REF = (SELECT
                                        DISCRIMINATOR_ID
                                      FROM
                                        DISCRIMINATOR
                                      WHERE
                                        NAME = "STRUCT"
                                      AND
                                        PACKAGE_REF
                                      IN
                                        (2))
     AND
       DATA_TYPE_CLUSTER.CLUSTER_CODE = ?
  )
        END AS STRUCT_ID),
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    ?,
    (SELECT
      DATA_TYPE_ID
     FROM
      DATA_TYPE
     WHERE
      DATA_TYPE.PACKAGE_REF IN (2)
      AND DATA_TYPE.NAME = ?)), values: ValveFreezeStateStruct,4294048769,ValveFreezeStateStruct,ValveFreezeStateStruct,4294048769,freezed,0,false,false,0,,,false,false,false,false,,boolean, Error: SQLITE_CONSTRAINT: NOT NULL constraint failed: STRUCT_ITEM.DATA_TYPE_REF
FATAL ERROR: Error::ThrowAsJavaScriptException napi_throw
----- Native stack trace -----

FATAL ERROR: command exited with status -6: /home/champon/ncs/v3.1.1/modules/lib/matter/.zap-install/zap /home/champon/Develop/Products/GV1/src/default_zap/GV1.zap --zcl /home/champon/Develop/Products/GV1/src/default_zap/zcl.json --gen /home/champon/ncs/v3.1.1/modules/lib/matter/src/app/zap-templates/app-templates.json --tempState



And after I execute the zap-append command:

west zap-append -o src/default_zap/zcl.json src/default_zap/WaterValveData.xml

I can execute the 

west zap-gui -j src/default_zap/zcl.json --clusters src/default_zap/WaterValveData.xml
successfully.

But I don't see your document ask me to execute the zap-append.


After  adding the custom device, and use the following command to generate the code.

 

west zap-generate --full

I write a zcl_callbacks.cpp file to implement the generated commands.

#include <app/ConcreteAttributePath.h>
#include <lib/support/logging/CHIPLogging.h>
#include "platform/CHIPDeviceLayer.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/callback.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <sys/_stdint.h>
#include <zap-generated/PluginApplicationCallbacks.h>
#include <zap-generated/clusters/WaterValveData/Commands.h>

#include "app/data-model/List.h"
#include "app/task_executor.h"
#include "clusters/WaterValveData/Enums.h"
#include "clusters/WaterValveData/Structs.h"
#include "lib/support/logging/TextOnlyLogging.h"

using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;

void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath &attributePath,
                                       uint8_t type,
                                       uint16_t size,
                                       uint8_t *value)
{
    ClusterId clusterId = attributePath.mClusterId;
    AttributeId attributeId = attributePath.mAttributeId;
    EndpointId endpointId = attributePath.mEndpointId;

    uint32_t value32 = 0;

    if (size == 1)
        value32 = *value;
    else if (size == 2)
        value32 = *reinterpret_cast<uint16_t *>(value);
    else if (size == 4)
        value32 = *reinterpret_cast<uint32_t *>(value);

    ChipLogProgress(DeviceLayer,
                    "====================Attribute change on endpoint %d, clusterId: %" PRIu32 ", attributeId: %" PRIu32
                    ", type: %d, size: %d, value: %d",
                    endpointId,
                    clusterId,
                    attributeId,
                    type,
                    size,
                    value32);
}

bool emberAfWaterValveDataClusterGetWaterStatsCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetWaterStats::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer,
                    "[%s] Water stats requested, stats = %d last = %d",
                    __FUNCTION__,
                    static_cast<int>(commandData.stats),
                    commandData.last);

    return true;
}

bool emberAfWaterValveDataClusterGetWaterUsageCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetWaterUsage::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer, "[%s] Water usage requested", __FUNCTION__);
    return true;
}

bool emberAfWaterValveDataClusterSetValveSessionCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::SetValveSession::DecodableType &commandData)
{
    ChipLogProgress(
        DeviceLayer,
        "[%s] Set Valve session requested - hour: %u, minute: %u duration: %u weekdays: %u, volume: %u, operation: %d",
        __FUNCTION__,
        commandData.session.hour,
        commandData.session.minute,
        commandData.session.duration,
        commandData.session.weekdays,
        commandData.session.volume,
        static_cast<int>(commandData.operation));
    return true;
}

bool emberAfWaterValveDataClusterGetValveSessionLogsCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetValveSessionLogs::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer, "[%s] Get valve session logs page:%d", __FUNCTION__, commandData.page);
    return true;
}

bool emberAfWaterValveDataClusterGetAllValveSessionCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetAllValveSession::DecodableType &commandData)
{
    return true;
}

bool emberAfWaterValveDataClusterOpenValveWithVolumeCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::OpenValveWithVolume::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer,
                    "[%s] Open valve with volume: %d.%dL",
                    __FUNCTION__,
                    commandData.volume / 10,
                    commandData.volume % 10);
    return true;
}

bool emberAfWaterValveDataClusterGetValveTemperatureRecordCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetValveTemperatureRecord::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer, "[%s] Get valve temperature record page:%d", __FUNCTION__, commandData.page);
    return true;
}

bool emberAfWaterValveDataClusterGetValveFreezeRecordCallback(
    chip::app::CommandHandler *commandObj,
    const chip::app::ConcreteCommandPath &commandPath,
    const chip::app::Clusters::WaterValveData::Commands::GetValveFreezeRecord::DecodableType &commandData)
{
    ChipLogProgress(DeviceLayer, "[%s] Get valve freeze record page:%d", __FUNCTION__, commandData.page);
    return true;
}

void MatterWaterValveDataPluginServerInitCallback()
{
    return;
}

void emberAfTemperatureMeasurementClusterInitCallback(chip::EndpointId endpoint)
{
    ChipLogProgress(DeviceLayer, "[%s] Init temperature measurement cluster", __FUNCTION__);
}

void emberAfBooleanStateClusterServerInitCallback(chip::EndpointId endpoint)
{
}

void emberAfValveConfigurationAndControlClusterServerInitCallback(chip::EndpointId endpoint)
{
}

and here is my CMake file.

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr HINTS $ENV{ZEPHYR_BASE})

project(GV1)

include(${ZEPHYR_NRF_MODULE_DIR}/samples/matter/common/cmake/zap_helpers.cmake)
ncs_get_zap_parent_dir(ZAP_PARENT_DIR)

get_filename_component(CHIP_APP_ZAP_DIR ${ZAP_PARENT_DIR}/zap-generated REALPATH CACHE)

# Enable GNU STD support.
include(${ZEPHYR_CONNECTEDHOMEIP_MODULE_DIR}/config/nrfconnect/app/enable-gnu-std.cmake)

# NORDIC SDK APP START

# Include all source files that are located in the Matter common directory.
include(${ZEPHYR_NRF_MODULE_DIR}/samples/matter/common/cmake/source_common.cmake)

# Include Data Model utils
include(${ZEPHYR_NRF_MODULE_DIR}/samples/matter/common/cmake/data_model.cmake)

target_include_directories(app PRIVATE
    src
    ${ZAP_PARENT_DIR}
)

target_sources(app PRIVATE
    src/app_task.cpp
    src/main.cpp
    src/zcl_callbacks.cpp
)

ncs_configure_data_model(
    EXTERNAL_CLUSTERS "WATER_VALVE_DATA_CLUSTER"
)
# NORDIC SDK APP END


But compile failed with:
4034.error.txt

And there is my template project which may help you reproduce the problem.

GV1.zip

I want to know what's wrong with my step and how to solve this problem.

Best regards,

Xiongwei.Wang

Related