diff --git a/examples/valve/controller/README.md b/examples/valve/controller/README.md new file mode 100644 index 00000000000000..270bf83cc04307 --- /dev/null +++ b/examples/valve/controller/README.md @@ -0,0 +1,73 @@ +# Running the drinks machine app and controller + +To run this example matter application, you need a version of the example app +and a controller. The example app can be compiled with gn / ninja, the +controller functions can be used through the chip-repl python shell + +## App + +### Compiling the app on linux + +From the root of the chip tree + +``` +cd examples/valve/linux +gn gen out/debug +ninja -C out/debug +``` + +### Running the app on linux + +From the root of the chip tree + +``` +./examples/valve/linux/out/debug/valve-app --KVS /tmp/valve_kvs.json +``` + +The KVS file is where the commissioning data is stored for the app, so be sure +to start the app with the same kvs file if you do not want to re-commission it +every time. All the standard linux app flags also work for this app. Use --help +to see the available options. + +## Controller + +### Compiling the chip-repl + +To compile the chip-repl, from the root of the chip tree: + +``` +. scripts/activate.sh +./scripts/build_python.sh -i out/pyenv +source out/pyenv/activate +out/pyenv/bin/chip-repl +``` + +The chip-repl is a shell that lets you directly call python functions. It +instantiates a controller object that can be used to communicate with devices. +The controller is called devCtrl. By default, its KVS is at +/tmp/repl-storage.json, but this can be changed on the command line if desired. +Use --help to see options. + +### Commissioning the valve app + +As long as the controller and the application KVSs are kept constant, the app +should only need to be commissioned once. + +To commission the device use: + +``` +from chip import ChipDeviceCtrl +await devCtrl.CommissionOnNetwork(nodeId=1, setupPinCode=20202021, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=3840) +``` + +### Interacting with the valve app + +To create a drinks machine controller: + +``` +import examples.valve.controller as DrinksController +dm = DrinksController.DrinkMachine(devCtrl, 1) +``` + +You can now call functions on the drinks machine controller. Tab completion will +work within the repl, so you can see the available options. diff --git a/examples/valve/controller/__init__.py b/examples/valve/controller/__init__.py new file mode 100644 index 00000000000000..2e415689beb7cd --- /dev/null +++ b/examples/valve/controller/__init__.py @@ -0,0 +1,146 @@ +import random + +from enum import StrEnum +from chip import ChipDeviceCtrl +from chip.clusters.Types import NullValue +import chip.clusters as Clusters + + +MYSTERY_DRINK = "mystery mirrorball" + + +class Bottle(StrEnum): + kBourbon = "bourbon" + kGin = "gin" + kCampari = "campari" + kVermouth = "vermouth" + kSourMix = "sour mix" + kSimpleSyrup = "simple syrup", + + +class Oz: + def __init__(self, oz: float): + self.oz = oz + + def time(self): + # One oz == approx 12s. + return self.oz * 12 + + def __eq__(self, other): + return self.oz == other.oz + + def __lt__(self, other): + return self.oz < other.oz + + +class DrinkMachine: + def __init__(self, devCtrl: ChipDeviceCtrl, node_id: int): + self.dev_ctrl = devCtrl + self.node_id = node_id + # TODO: Should this actually be modelled as an aggregator with bridged nodes so I can have a NodeLabel? + # Right now I'm going to leave this as something on the app side because it's a demo, but it's weird that we don't have writeable labels unless you model it strangely. Spec issue? + self.bottles: dict[Bottle, int] = {Bottle.kBourbon: 1, Bottle.kGin: 2, + Bottle.kCampari: 3, Bottle.kVermouth: 4, Bottle.kSourMix: 5, Bottle.kSimpleSyrup: 6} + self.recipes: dict[str, dict[Bottle, Oz]] = {} + self.add_recipe("negroni", {Bottle.kGin: Oz(1.5), Bottle.kCampari: Oz(1.5), Bottle.kVermouth: Oz(1.5)}) + self.add_recipe("boulevardier", {Bottle.kBourbon: Oz(1.5), Bottle.kCampari: Oz(1.5), Bottle.kVermouth: Oz(1.5)}) + self.add_recipe("bourbon sour", {Bottle.kBourbon: Oz(2), Bottle.kSourMix: Oz(1), Bottle.kSimpleSyrup: Oz(1.5)}) + self.add_recipe("martini", {Bottle.kGin: Oz(2), Bottle.kVermouth: Oz(0.25)}) + self.add_recipe("gimlet", {Bottle.kGin: Oz(2.5), Bottle.kSourMix: Oz(0.5), Bottle.kSimpleSyrup: Oz(0.5)}) + self.add_recipe("old fashioned", {Bottle.kBourbon: Oz(2), Bottle.kSimpleSyrup: Oz(0.125)}) + self.add_recipe("shot of bourbon", {Bottle.kBourbon: Oz(1.5)}) + self.add_recipe("shot of gin", {Bottle.kGin: Oz(1.5)}) + self.add_recipe("gin sour", {Bottle.kGin: Oz(2), Bottle.kSourMix: Oz(1), Bottle.kSimpleSyrup: Oz(0.5)}) + self.add_recipe("manhattan", {Bottle.kBourbon: Oz(2), Bottle.kVermouth: Oz(1)}) + self.add_recipe("campari sour", {Bottle.kGin: Oz(1), Bottle.kCampari: Oz(1), + Bottle.kSourMix: Oz(0.75), Bottle.kSimpleSyrup: Oz(0.5)}) + self.add_recipe(MYSTERY_DRINK, {}) + + def set_bottle_names(self, bottles: dict[Bottle, int]) -> bool: + ''' Bottle is a dict of bottle name to endpoint and should contain all 6 endpoints at once''' + if len(bottles) != 6: + return False + self.bottles = bottles + + def get_bottle_names(self): + return self.bottles + + def get_recipes(self): + return self.recipes + + def add_recipe(self, name: str, ingredients: dict[Bottle, Oz]): + # TODO: should store somewhere permanent - simplest is to write out to file. In the meanwhile, we have a few pre-populated + self.recipes[name] = ingredients + + async def _dispense_mystery(self): + num_ingredients = random.randint(1, 3) + bottles = set() + choice = random.choice(list(Bottle)) + bottles.add(choice) + while len(bottles) < num_ingredients: + # If this matches something in the bottle list already, it won't be added + choice = random.choice(list(Bottle)) + bottles.add(choice) + + # we're going to aim for a 2.5 Oz shot with min 0.5 shot sizes + goal = 2.5 + print(f"Creating your mystery beverage:") + ingredients = {} + total = 0 + for bottle in bottles: + remaining = num_ingredients - len(ingredients.keys()) + if remaining == 1: + oz = goal-total + else: + max_oz = goal - total - 0.5*(remaining-1) + # multiply by 2 because randrange likes ints + oz = random.randrange(1, max_oz*2, 1)/2 + total += oz + ingredients[bottle] = Oz(oz) + print(f"\t{oz} Oz of {bottle}") + + largest = max(ingredients, key=ingredients.get) + mystery_prefix = ['', 'giant', 'potent', 'disco-tastic', 'shiny', 'Dr.'] + mystery_suffix = ['blaster', 'concoction', 'blend', 'cocktail'] + mystery_extra_suffix = ['', 'jr', 'of wonder', 'esq'] + name = [] + name.append(random.choice(mystery_prefix)) + name.append(largest) + name.append(random.choice(mystery_suffix)) + name.append(random.choice(mystery_extra_suffix)) + print(f'The {" ".join(name).strip()}') + print('Please enjoy responsibly.') + await self._dispense_ingredients(ingredients) + + async def dispense(self, recipe: str): + if recipe == MYSTERY_DRINK: + return await self._dispense_mystery() + + # TODO: be a bit nicer on the comparison here. Strings as keys aren't great, but I want the flexibility to add non-standard recipes + if recipe not in self.recipes.keys(): + print(f"Unable to find the specified recipe. Available Recipes: {self.recipes.keys()}") + return + required_bottles = set(self.recipes[recipe].keys()) + if not required_bottles.issubset(set(self.bottles)): + print('Recipe requires an ingredient that is not loaded into the drink machine') + print(f'Recipe requires: {required_bottles}') + print(f'Available: {self.bottles}') + return + + ingredients = self.recipes[recipe] + self._dispense_ingredients(ingredients) + + async def _dispense_ingredients(self, ingredients: dict[Bottle, Oz]): + for bottle, amount in ingredients.items(): + ep = self.bottles[bottle] + time = amount.time() + await self.dev_ctrl.SendCommand(nodeid=self.node_id, endpoint=ep, + payload=Clusters.ValveConfigurationAndControl.Commands.Open(openDuration=time)) + + async def prime(self, endpoint: int): + await self.dev_ctrl.SendCommand(nodeid=self.node_id, endpoint=endpoint, + payload=Clusters.ValveConfigurationAndControl.Commands.Open(openDuration=NullValue)) + + async def stop(self, endpoint: int): + await self.dev_ctrl.SendCommand(nodeid=self.node_id, endpoint=endpoint, + payload=Clusters.ValveConfigurationAndControl.Commands.Close()) diff --git a/examples/valve/linux/.gn b/examples/valve/linux/.gn new file mode 100644 index 00000000000000..5d1ce757507582 --- /dev/null +++ b/examples/valve/linux/.gn @@ -0,0 +1,25 @@ +# Copyright (c) 2020 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. + +import("//build_overrides/build.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + import("//args.gni") +} diff --git a/examples/valve/linux/BUILD.gn b/examples/valve/linux/BUILD.gn new file mode 100644 index 00000000000000..d4a43214538098 --- /dev/null +++ b/examples/valve/linux/BUILD.gn @@ -0,0 +1,55 @@ +# 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. + +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/src/app/common_flags.gni") + +assert(chip_build_tools) + +import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni") + +config("includes") { + include_dirs = [ + ".", + "include", + ] +} + +executable("valve-app") { + sources = [ + "include/CHIPProjectAppConfig.h", + "main.cpp", + ] + + deps = [ + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/examples/valve/valve-common", + "${chip_root}/src/lib", + ] + + cflags = [ "-Wconversion" ] + + include_dirs = [ "include" ] + output_dir = root_out_dir +} + +group("linux") { + deps = [ ":valve-app" ] +} + +group("default") { + deps = [ ":linux" ] +} diff --git a/examples/valve/linux/args.gni b/examples/valve/linux/args.gni new file mode 100644 index 00000000000000..b91c0bafdfc9aa --- /dev/null +++ b/examples/valve/linux/args.gni @@ -0,0 +1,25 @@ +# 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. + +import("//build_overrides/chip.gni") + +import("${chip_root}/config/standalone/args.gni") + +chip_device_project_config_include = "" +chip_project_config_include = "" +chip_system_project_config_include = "" + +chip_project_config_include_dirs = + [ "${chip_root}/examples/air-purifier-app/linux/include" ] +chip_project_config_include_dirs += [ "${chip_root}/config/standalone" ] diff --git a/examples/valve/linux/build_overrides b/examples/valve/linux/build_overrides new file mode 120000 index 00000000000000..e578e73312ebd1 --- /dev/null +++ b/examples/valve/linux/build_overrides @@ -0,0 +1 @@ +../../build_overrides \ No newline at end of file diff --git a/examples/valve/linux/include/CHIPProjectAppConfig.h b/examples/valve/linux/include/CHIPProjectAppConfig.h new file mode 100644 index 00000000000000..4db52afa30ad47 --- /dev/null +++ b/examples/valve/linux/include/CHIPProjectAppConfig.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +// include the CHIPProjectConfig from config/standalone +#include + +#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 0 + +#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT 0 + +#define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 + +#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_TYPE 1 + +#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0x0042 // Water valve + +#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_NAME 1 + +#define CHIP_DEVICE_ENABLE_PORT_PARAMS 1 + +#define CHIP_DEVICE_CONFIG_DEVICE_NAME "DISCO Drinks" diff --git a/examples/valve/linux/main.cpp b/examples/valve/linux/main.cpp new file mode 100644 index 00000000000000..83df6e1d7d746e --- /dev/null +++ b/examples/valve/linux/main.cpp @@ -0,0 +1,168 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::ValveConfigurationAndControl; +namespace { +class PrintOnlyDelegate : public NonLevelControlDelegate +{ +public: + PrintOnlyDelegate(EndpointId endpoint) : mEndpoint(endpoint) {} + CHIP_ERROR HandleOpenValve(ValveStateEnum & currentState, BitMask & valveFault) override + { + ChipLogError(NotSpecified, "\n\nVALVE IS OPENING on endpoint %u!!!!!\n\n", mEndpoint); + state = ValveStateEnum::kOpen; + currentState = state; + return CHIP_NO_ERROR; + } + ValveStateEnum GetCurrentValveState() override { return state; } + CHIP_ERROR HandleCloseValve(ValveStateEnum & currentState, BitMask & valveFault) override + { + ChipLogError(NotSpecified, "\n\nVALVE IS CLOSING on endpoint %u!!!!!\n\n", mEndpoint); + state = ValveStateEnum::kClosed; + currentState = state; + return CHIP_NO_ERROR; + } + +private: + ValveStateEnum state = ValveStateEnum::kClosed; + EndpointId mEndpoint; +}; + +// TODO: Does this conflict with the kvs delegate that's statically allocated in the AppMain? +// I think as long as the manager is the same, we're ok, but need to confirm. +KvsPersistentStorageDelegate sStorage; + +class NonLevelValveEndpoint +{ +public: + NonLevelValveEndpoint(EndpointId endpoint) : + mEndpoint(endpoint), mContext(mEndpoint, sStorage), mDelegate(mEndpoint), mLogic(mDelegate, mContext), + mInterface(mEndpoint, mLogic) + {} + CHIP_ERROR Init() + { + ReturnErrorOnFailure(mLogic.Init(kConformance, kInitParams)); + ReturnErrorOnFailure(mInterface.Init()); + return CHIP_NO_ERROR; + } + +private: + const ClusterConformance kConformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false + }; + const ClusterInitParameters kInitParams = { .currentState = DataModel::MakeNullable(ValveStateEnum::kClosed), + .currentLevel = DataModel::NullNullable, + .valveFault = 0, + .levelStep = 1 }; + EndpointId mEndpoint; + MatterContext mContext; + PrintOnlyDelegate mDelegate; + ClusterLogic mLogic; + Interface mInterface; +}; + +NonLevelValveEndpoint ep1(1); +NonLevelValveEndpoint ep2(2); +NonLevelValveEndpoint ep3(3); +NonLevelValveEndpoint ep4(4); +NonLevelValveEndpoint ep5(5); +NonLevelValveEndpoint ep6(6); + +// from https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces/Namespace-Common-Position.adoc +constexpr const uint8_t kNamespaceCommonPosition = 0x8; +constexpr const uint8_t kNamespaceCommonPositionRow = 0x5; + +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp1TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("1"_span)) }, +}; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp2TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("2"_span)) }, +}; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp3TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("3"_span)) }, +}; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp4TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("4"_span)) }, +}; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp5TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("5"_span)) }, +}; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp6TagList[] = { + { .namespaceID = kNamespaceCommonPosition, + .tag = kNamespaceCommonPositionRow, + .label = chip::MakeOptional(DataModel::Nullable("6"_span)) }, +}; +} // namespace + +void ApplicationInit() +{ + ChipLogError(NotSpecified, "App init!!!"); + chip::DeviceLayer::PersistedStorage::KeyValueStoreManager & kvsManager = DeviceLayer::PersistedStorage::KeyValueStoreMgr(); + sStorage.Init(&kvsManager); + + ep1.Init(); + ep2.Init(); + ep3.Init(); + ep4.Init(); + ep5.Init(); + ep6.Init(); + // TODO: Can we pull these from the command line or something so these can be swapped on the fly? + SetTagList(/* endpoint= */ 1, Span(gEp1TagList)); + SetTagList(/* endpoint= */ 2, Span(gEp2TagList)); + SetTagList(/* endpoint= */ 3, Span(gEp3TagList)); + SetTagList(/* endpoint= */ 4, Span(gEp4TagList)); + SetTagList(/* endpoint= */ 5, Span(gEp5TagList)); + SetTagList(/* endpoint= */ 6, Span(gEp6TagList)); +} + +void ApplicationShutdown() +{ + ChipLogDetail(NotSpecified, "ApplicationShutdown()"); +} + +int main(int argc, char * argv[]) +{ + if (ChipLinuxAppInit(argc, argv) != 0) + { + return -1; + } + + ChipLinuxAppMainLoop(); + return 0; +} diff --git a/examples/valve/linux/third_party/connectedhomeip b/examples/valve/linux/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/valve/linux/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file diff --git a/examples/valve/valve-common/BUILD.gn b/examples/valve/valve-common/BUILD.gn new file mode 100755 index 00000000000000..d74baf0ee7c197 --- /dev/null +++ b/examples/valve/valve-common/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright (c) 2020 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. + +import("//build_overrides/chip.gni") +import("${chip_root}/src/app/chip_data_model.gni") + +chip_data_model("valve-common") { + zap_file = "valve-app.zap" + is_server = true +} diff --git a/examples/valve/valve-common/valve-app.matter b/examples/valve/valve-common/valve-app.matter new file mode 100644 index 00000000000000..392adac7c19067 --- /dev/null +++ b/examples/valve/valve-common/valve-app.matter @@ -0,0 +1,1709 @@ +// This IDL was generated automatically by ZAP. +// It is for view/code review purposes only. + +enum AreaTypeTag : enum8 { + kAisle = 0; + kAttic = 1; + kBackDoor = 2; + kBackYard = 3; + kBalcony = 4; + kBallroom = 5; + kBathroom = 6; + kBedroom = 7; + kBorder = 8; + kBoxroom = 9; + kBreakfastRoom = 10; + kCarport = 11; + kCellar = 12; + kCloakroom = 13; + kCloset = 14; + kConservatory = 15; + kCorridor = 16; + kCraftRoom = 17; + kCupboard = 18; + kDeck = 19; + kDen = 20; + kDining = 21; + kDrawingRoom = 22; + kDressingRoom = 23; + kDriveway = 24; + kElevator = 25; + kEnsuite = 26; + kEntrance = 27; + kEntryway = 28; + kFamilyRoom = 29; + kFoyer = 30; + kFrontDoor = 31; + kFrontYard = 32; + kGameRoom = 33; + kGarage = 34; + kGarageDoor = 35; + kGarden = 36; + kGardenDoor = 37; + kGuestBathroom = 38; + kGuestBedroom = 39; + kGuestRestroom = 40; + kGuestRoom = 41; + kGym = 42; + kHallway = 43; + kHearthRoom = 44; + kKidsRoom = 45; + kKidsBedroom = 46; + kKitchen = 47; + kLarder = 48; + kLaundryRoom = 49; + kLawn = 50; + kLibrary = 51; + kLivingRoom = 52; + kLounge = 53; + kMediaTVRoom = 54; + kMudRoom = 55; + kMusicRoom = 56; + kNursery = 57; + kOffice = 58; + kOutdoorKitchen = 59; + kOutside = 60; + kPantry = 61; + kParkingLot = 62; + kParlor = 63; + kPatio = 64; + kPlayRoom = 65; + kPoolRoom = 66; + kPorch = 67; + kPrimaryBathroom = 68; + kPrimaryBedroom = 69; + kRamp = 70; + kReceptionRoom = 71; + kRecreationRoom = 72; + kRestroom = 73; + kRoof = 74; + kSauna = 75; + kScullery = 76; + kSewingRoom = 77; + kShed = 78; + kSideDoor = 79; + kSideYard = 80; + kSittingRoom = 81; + kSnug = 82; + kSpa = 83; + kStaircase = 84; + kSteamRoom = 85; + kStorageRoom = 86; + kStudio = 87; + kStudy = 88; + kSunRoom = 89; + kSwimmingPool = 90; + kTerrace = 91; + kUtilityRoom = 92; + kWard = 93; + kWorkshop = 94; +} + +enum AtomicRequestTypeEnum : enum8 { + kBeginWrite = 0; + kCommitWrite = 1; + kRollbackWrite = 2; +} + +enum FloorSurfaceTag : enum8 { + kCarpet = 0; + kCeramic = 1; + kConcrete = 2; + kCork = 3; + kDeepCarpet = 4; + kDirt = 5; + kEngineeredWood = 6; + kGlass = 7; + kGrass = 8; + kHardwood = 9; + kLaminate = 10; + kLinoleum = 11; + kMat = 12; + kMetal = 13; + kPlastic = 14; + kPolishedConcrete = 15; + kRubber = 16; + kRug = 17; + kSand = 18; + kStone = 19; + kTatami = 20; + kTerrazzo = 21; + kTile = 22; + kVinyl = 23; +} + +enum LandmarkTag : enum8 { + kAirConditioner = 0; + kAirPurifier = 1; + kBackDoor = 2; + kBarStool = 3; + kBathMat = 4; + kBathtub = 5; + kBed = 6; + kBookshelf = 7; + kChair = 8; + kChristmasTree = 9; + kCoatRack = 10; + kCoffeeTable = 11; + kCookingRange = 12; + kCouch = 13; + kCountertop = 14; + kCradle = 15; + kCrib = 16; + kDesk = 17; + kDiningTable = 18; + kDishwasher = 19; + kDoor = 20; + kDresser = 21; + kLaundryDryer = 22; + kFan = 23; + kFireplace = 24; + kFreezer = 25; + kFrontDoor = 26; + kHighChair = 27; + kKitchenIsland = 28; + kLamp = 29; + kLitterBox = 30; + kMirror = 31; + kNightstand = 32; + kOven = 33; + kPetBed = 34; + kPetBowl = 35; + kPetCrate = 36; + kRefrigerator = 37; + kScratchingPost = 38; + kShoeRack = 39; + kShower = 40; + kSideDoor = 41; + kSink = 42; + kSofa = 43; + kStove = 44; + kTable = 45; + kToilet = 46; + kTrashCan = 47; + kLaundryWasher = 48; + kWindow = 49; + kWineCooler = 50; +} + +enum PositionTag : enum8 { + kLeft = 0; + kRight = 1; + kTop = 2; + kBottom = 3; + kMiddle = 4; + kRow = 5; + kColumn = 6; +} + +enum RelativePositionTag : enum8 { + kUnder = 0; + kNextTo = 1; + kAround = 2; + kOn = 3; + kAbove = 4; + kFrontOf = 5; + kBehind = 6; +} + +enum TestGlobalEnum : enum8 { + kSomeValue = 0; + kSomeOtherValue = 1; + kFinalValue = 2; +} + +bitmap TestGlobalBitmap : bitmap32 { + kFirstBit = 0x1; + kSecondBit = 0x2; +} + +struct TestGlobalStruct { + char_string<128> name = 0; + nullable TestGlobalBitmap myBitmap = 1; + optional nullable TestGlobalEnum myEnum = 2; +} + +struct LocationDescriptorStruct { + char_string<128> locationName = 0; + nullable int16s floorNumber = 1; + nullable AreaTypeTag areaType = 2; +} + +struct AtomicAttributeStatusStruct { + attrib_id attributeID = 0; + status statusCode = 1; +} + +/** Attributes and commands for putting a device into Identification mode (e.g. flashing a light). */ +cluster Identify = 3 { + revision 4; + + enum EffectIdentifierEnum : enum8 { + kBlink = 0; + kBreathe = 1; + kOkay = 2; + kChannelChange = 11; + kFinishEffect = 254; + kStopEffect = 255; + } + + enum EffectVariantEnum : enum8 { + kDefault = 0; + } + + enum IdentifyTypeEnum : enum8 { + kNone = 0; + kLightOutput = 1; + kVisibleIndicator = 2; + kAudibleBeep = 3; + kDisplay = 4; + kActuator = 5; + } + + attribute int16u identifyTime = 0; + readonly attribute IdentifyTypeEnum identifyType = 1; + 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 IdentifyRequest { + int16u identifyTime = 0; + } + + request struct TriggerEffectRequest { + EffectIdentifierEnum effectIdentifier = 0; + EffectVariantEnum effectVariant = 1; + } + + /** Command description for Identify */ + command access(invoke: manage) Identify(IdentifyRequest): DefaultSuccess = 0; + /** Command description for TriggerEffect */ + command access(invoke: manage) TriggerEffect(TriggerEffectRequest): DefaultSuccess = 64; +} + +/** The Descriptor Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for describing a node, its endpoints and clusters. */ +cluster Descriptor = 29 { + revision 2; + + bitmap Feature : bitmap32 { + kTagList = 0x1; + } + + struct DeviceTypeStruct { + devtype_id deviceType = 0; + int16u revision = 1; + } + + struct SemanticTagStruct { + nullable vendor_id mfgCode = 0; + enum8 namespaceID = 1; + enum8 tag = 2; + optional nullable char_string label = 3; + } + + readonly attribute DeviceTypeStruct deviceTypeList[] = 0; + readonly attribute cluster_id serverList[] = 1; + readonly attribute cluster_id clientList[] = 2; + readonly attribute endpoint_no partsList[] = 3; + readonly attribute optional SemanticTagStruct tagList[] = 4; + 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; +} + +/** The Access Control Cluster exposes a data model view of a + Node's Access Control List (ACL), which codifies the rules used to manage + and enforce Access Control for the Node's endpoints and their associated + cluster instances. */ +cluster AccessControl = 31 { + revision 2; + + enum AccessControlEntryAuthModeEnum : enum8 { + kPASE = 1; + kCASE = 2; + kGroup = 3; + } + + enum AccessControlEntryPrivilegeEnum : enum8 { + kView = 1; + kProxyView = 2; + kOperate = 3; + kManage = 4; + kAdminister = 5; + } + + enum AccessRestrictionTypeEnum : enum8 { + kAttributeAccessForbidden = 0; + kAttributeWriteForbidden = 1; + kCommandForbidden = 2; + kEventForbidden = 3; + } + + enum ChangeTypeEnum : enum8 { + kChanged = 0; + kAdded = 1; + kRemoved = 2; + } + + bitmap Feature : bitmap32 { + kExtension = 0x1; + kManagedDevice = 0x2; + } + + struct AccessRestrictionStruct { + AccessRestrictionTypeEnum type = 0; + nullable int32u id = 1; + } + + struct CommissioningAccessRestrictionEntryStruct { + endpoint_no endpoint = 0; + cluster_id cluster = 1; + AccessRestrictionStruct restrictions[] = 2; + } + + fabric_scoped struct AccessRestrictionEntryStruct { + fabric_sensitive endpoint_no endpoint = 0; + fabric_sensitive cluster_id cluster = 1; + fabric_sensitive AccessRestrictionStruct restrictions[] = 2; + fabric_idx fabricIndex = 254; + } + + struct AccessControlTargetStruct { + nullable cluster_id cluster = 0; + nullable endpoint_no endpoint = 1; + nullable devtype_id deviceType = 2; + } + + fabric_scoped struct AccessControlEntryStruct { + fabric_sensitive AccessControlEntryPrivilegeEnum privilege = 1; + fabric_sensitive AccessControlEntryAuthModeEnum authMode = 2; + nullable fabric_sensitive int64u subjects[] = 3; + nullable fabric_sensitive AccessControlTargetStruct targets[] = 4; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct AccessControlExtensionStruct { + fabric_sensitive octet_string<128> data = 1; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlEntryChanged = 0 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlEntryStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlExtensionChanged = 1 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlExtensionStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) FabricRestrictionReviewUpdate = 2 { + int64u token = 0; + optional long_char_string instruction = 1; + optional long_char_string ARLRequestFlowUrl = 2; + fabric_idx fabricIndex = 254; + } + + attribute access(read: administer, write: administer) AccessControlEntryStruct acl[] = 0; + attribute access(read: administer, write: administer) optional AccessControlExtensionStruct extension[] = 1; + readonly attribute int16u subjectsPerAccessControlEntry = 2; + readonly attribute int16u targetsPerAccessControlEntry = 3; + readonly attribute int16u accessControlEntriesPerFabric = 4; + readonly attribute optional CommissioningAccessRestrictionEntryStruct commissioningARL[] = 5; + readonly attribute optional AccessRestrictionEntryStruct arl[] = 6; + 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 ReviewFabricRestrictionsRequest { + CommissioningAccessRestrictionEntryStruct arl[] = 0; + } + + response struct ReviewFabricRestrictionsResponse = 1 { + int64u token = 0; + } + + /** This command signals to the service associated with the device vendor that the fabric administrator would like a review of the current restrictions on the accessing fabric. */ + fabric command access(invoke: administer) ReviewFabricRestrictions(ReviewFabricRestrictionsRequest): ReviewFabricRestrictionsResponse = 0; +} + +/** This cluster provides attributes and events for determining basic information about Nodes, which supports both + Commissioning and operational determination of Node characteristics, such as Vendor ID, Product ID and serial number, + which apply to the whole Node. Also allows setting user device information such as location. */ +cluster BasicInformation = 40 { + revision 3; + + enum ColorEnum : enum8 { + kBlack = 0; + kNavy = 1; + kGreen = 2; + kTeal = 3; + kMaroon = 4; + kPurple = 5; + kOlive = 6; + kGray = 7; + kBlue = 8; + kLime = 9; + kAqua = 10; + kRed = 11; + kFuchsia = 12; + kYellow = 13; + kWhite = 14; + kNickel = 15; + kChrome = 16; + kBrass = 17; + kCopper = 18; + kSilver = 19; + kGold = 20; + } + + enum ProductFinishEnum : enum8 { + kOther = 0; + kMatte = 1; + kSatin = 2; + kPolished = 3; + kRugged = 4; + kFabric = 5; + } + + struct CapabilityMinimaStruct { + int16u caseSessionsPerFabric = 0; + int16u subscriptionsPerFabric = 1; + } + + struct ProductAppearanceStruct { + ProductFinishEnum finish = 0; + nullable ColorEnum primaryColor = 1; + } + + critical event StartUp = 0 { + int32u softwareVersion = 0; + } + + critical event ShutDown = 1 { + } + + info event Leave = 2 { + fabric_idx fabricIndex = 0; + } + + info event ReachableChanged = 3 { + boolean reachableNewValue = 0; + } + + readonly attribute int16u dataModelRevision = 0; + readonly attribute char_string<32> vendorName = 1; + readonly attribute vendor_id vendorID = 2; + readonly attribute char_string<32> productName = 3; + readonly attribute int16u productID = 4; + attribute access(write: manage) char_string<32> nodeLabel = 5; + attribute access(write: administer) char_string<2> location = 6; + readonly attribute int16u hardwareVersion = 7; + readonly attribute char_string<64> hardwareVersionString = 8; + readonly attribute int32u softwareVersion = 9; + readonly attribute char_string<64> softwareVersionString = 10; + readonly attribute optional char_string<16> manufacturingDate = 11; + readonly attribute optional char_string<32> partNumber = 12; + readonly attribute optional long_char_string<256> productURL = 13; + readonly attribute optional char_string<64> productLabel = 14; + readonly attribute optional char_string<32> serialNumber = 15; + attribute access(write: manage) optional boolean localConfigDisabled = 16; + readonly attribute optional boolean reachable = 17; + readonly attribute char_string<32> uniqueID = 18; + readonly attribute CapabilityMinimaStruct capabilityMinima = 19; + readonly attribute optional ProductAppearanceStruct productAppearance = 20; + readonly attribute int32u specificationVersion = 21; + readonly attribute int16u maxPathsPerInvoke = 22; + 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; + + command MfgSpecificPing(): DefaultSuccess = 0; +} + +/** This cluster is used to manage global aspects of the Commissioning flow. */ +cluster GeneralCommissioning = 48 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningErrorEnum : enum8 { + kOK = 0; + kValueOutsideRange = 1; + kInvalidAuthentication = 2; + kNoFailSafe = 3; + kBusyWithOtherAdmin = 4; + kRequiredTCNotAccepted = 5; + kTCAcknowledgementsNotReceived = 6; + kTCMinVersionNotMet = 7; + } + + enum RegulatoryLocationTypeEnum : enum8 { + kIndoor = 0; + kOutdoor = 1; + kIndoorOutdoor = 2; + } + + bitmap Feature : bitmap32 { + kTermsAndConditions = 0x1; + } + + struct BasicCommissioningInfo { + int16u failSafeExpiryLengthSeconds = 0; + int16u maxCumulativeFailsafeSeconds = 1; + } + + attribute access(write: administer) int64u breadcrumb = 0; + readonly attribute BasicCommissioningInfo basicCommissioningInfo = 1; + readonly attribute RegulatoryLocationTypeEnum regulatoryConfig = 2; + readonly attribute RegulatoryLocationTypeEnum locationCapability = 3; + readonly attribute boolean supportsConcurrentConnection = 4; + provisional readonly attribute access(read: administer) optional int16u TCAcceptedVersion = 5; + provisional readonly attribute access(read: administer) optional int16u TCMinRequiredVersion = 6; + provisional readonly attribute access(read: administer) optional bitmap16 TCAcknowledgements = 7; + provisional readonly attribute access(read: administer) optional boolean TCAcknowledgementsRequired = 8; + 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 ArmFailSafeRequest { + int16u expiryLengthSeconds = 0; + int64u breadcrumb = 1; + } + + response struct ArmFailSafeResponse = 1 { + CommissioningErrorEnum errorCode = 0; + char_string<128> debugText = 1; + } + + request struct SetRegulatoryConfigRequest { + RegulatoryLocationTypeEnum newRegulatoryConfig = 0; + char_string<2> countryCode = 1; + int64u breadcrumb = 2; + } + + response struct SetRegulatoryConfigResponse = 3 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + response struct CommissioningCompleteResponse = 5 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + request struct SetTCAcknowledgementsRequest { + int16u TCVersion = 0; + bitmap16 TCUserResponse = 1; + } + + response struct SetTCAcknowledgementsResponse = 7 { + CommissioningErrorEnum errorCode = 0; + } + + /** Arm the persistent fail-safe timer with an expiry time of now + ExpiryLengthSeconds using device clock */ + command access(invoke: administer) ArmFailSafe(ArmFailSafeRequest): ArmFailSafeResponse = 0; + /** Set the regulatory configuration to be used during commissioning */ + command access(invoke: administer) SetRegulatoryConfig(SetRegulatoryConfigRequest): SetRegulatoryConfigResponse = 2; + /** Signals the Server that the Client has successfully completed all steps of Commissioning/Recofiguration needed during fail-safe period. */ + fabric command access(invoke: administer) CommissioningComplete(): CommissioningCompleteResponse = 4; + /** This command sets the user acknowledgements received in the Enhanced Setup Flow Terms and Conditions into the node. */ + command access(invoke: administer) SetTCAcknowledgements(SetTCAcknowledgementsRequest): SetTCAcknowledgementsResponse = 6; +} + +/** Functionality to configure, enable, disable network credentials and access on a Matter device. */ +cluster NetworkCommissioning = 49 { + revision 1; // NOTE: Default/not specifically set + + enum NetworkCommissioningStatusEnum : enum8 { + kSuccess = 0; + kOutOfRange = 1; + kBoundsExceeded = 2; + kNetworkIDNotFound = 3; + kDuplicateNetworkID = 4; + kNetworkNotFound = 5; + kRegulatoryError = 6; + kAuthFailure = 7; + kUnsupportedSecurity = 8; + kOtherConnectionFailure = 9; + kIPV6Failed = 10; + kIPBindFailed = 11; + kUnknownError = 12; + } + + enum WiFiBandEnum : enum8 { + k2G4 = 0; + k3G65 = 1; + k5G = 2; + k6G = 3; + k60G = 4; + k1G = 5; + } + + bitmap Feature : bitmap32 { + kWiFiNetworkInterface = 0x1; + kThreadNetworkInterface = 0x2; + kEthernetNetworkInterface = 0x4; + kPerDeviceCredentials = 0x8; + } + + bitmap ThreadCapabilitiesBitmap : bitmap16 { + kIsBorderRouterCapable = 0x1; + kIsRouterCapable = 0x2; + kIsSleepyEndDeviceCapable = 0x4; + kIsFullThreadDevice = 0x8; + kIsSynchronizedSleepyEndDeviceCapable = 0x10; + } + + bitmap WiFiSecurityBitmap : bitmap8 { + kUnencrypted = 0x1; + kWEP = 0x2; + kWPAPersonal = 0x4; + kWPA2Personal = 0x8; + kWPA3Personal = 0x10; + kWPA3MatterPDC = 0x20; + } + + struct NetworkInfoStruct { + octet_string<32> networkID = 0; + boolean connected = 1; + optional nullable octet_string<20> networkIdentifier = 2; + optional nullable octet_string<20> clientIdentifier = 3; + } + + struct ThreadInterfaceScanResultStruct { + int16u panId = 0; + int64u extendedPanId = 1; + char_string<16> networkName = 2; + int16u channel = 3; + int8u version = 4; + octet_string<8> extendedAddress = 5; + int8s rssi = 6; + int8u lqi = 7; + } + + struct WiFiInterfaceScanResultStruct { + WiFiSecurityBitmap security = 0; + octet_string<32> ssid = 1; + octet_string<6> bssid = 2; + int16u channel = 3; + WiFiBandEnum wiFiBand = 4; + int8s rssi = 5; + } + + readonly attribute access(read: administer) int8u maxNetworks = 0; + readonly attribute access(read: administer) NetworkInfoStruct networks[] = 1; + readonly attribute optional int8u scanMaxTimeSeconds = 2; + readonly attribute optional int8u connectMaxTimeSeconds = 3; + attribute access(write: administer) boolean interfaceEnabled = 4; + readonly attribute access(read: administer) nullable NetworkCommissioningStatusEnum lastNetworkingStatus = 5; + readonly attribute access(read: administer) nullable octet_string<32> lastNetworkID = 6; + readonly attribute access(read: administer) nullable int32s lastConnectErrorValue = 7; + provisional readonly attribute optional WiFiBandEnum supportedWiFiBands[] = 8; + provisional readonly attribute optional ThreadCapabilitiesBitmap supportedThreadFeatures = 9; + provisional readonly attribute optional int16u threadVersion = 10; + 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 ScanNetworksRequest { + optional nullable octet_string<32> ssid = 0; + optional int64u breadcrumb = 1; + } + + response struct ScanNetworksResponse = 1 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + optional WiFiInterfaceScanResultStruct wiFiScanResults[] = 2; + optional ThreadInterfaceScanResultStruct threadScanResults[] = 3; + } + + request struct AddOrUpdateWiFiNetworkRequest { + octet_string<32> ssid = 0; + octet_string<64> credentials = 1; + optional int64u breadcrumb = 2; + optional octet_string<140> networkIdentity = 3; + optional octet_string<20> clientIdentifier = 4; + optional octet_string<32> possessionNonce = 5; + } + + request struct AddOrUpdateThreadNetworkRequest { + octet_string<254> operationalDataset = 0; + optional int64u breadcrumb = 1; + } + + request struct RemoveNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct NetworkConfigResponse = 5 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string<512> debugText = 1; + optional int8u networkIndex = 2; + optional octet_string<140> clientIdentity = 3; + optional octet_string<64> possessionSignature = 4; + } + + request struct ConnectNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct ConnectNetworkResponse = 7 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + nullable int32s errorValue = 2; + } + + request struct ReorderNetworkRequest { + octet_string<32> networkID = 0; + int8u networkIndex = 1; + optional int64u breadcrumb = 2; + } + + request struct QueryIdentityRequest { + octet_string<20> keyIdentifier = 0; + optional octet_string<32> possessionNonce = 1; + } + + response struct QueryIdentityResponse = 10 { + octet_string<140> identity = 0; + optional octet_string<64> possessionSignature = 1; + } + + /** Detemine the set of networks the device sees as available. */ + command access(invoke: administer) ScanNetworks(ScanNetworksRequest): ScanNetworksResponse = 0; + /** Add or update the credentials for a given Wi-Fi network. */ + command access(invoke: administer) AddOrUpdateWiFiNetwork(AddOrUpdateWiFiNetworkRequest): NetworkConfigResponse = 2; + /** Add or update the credentials for a given Thread network. */ + command access(invoke: administer) AddOrUpdateThreadNetwork(AddOrUpdateThreadNetworkRequest): NetworkConfigResponse = 3; + /** Remove the definition of a given network (including its credentials). */ + command access(invoke: administer) RemoveNetwork(RemoveNetworkRequest): NetworkConfigResponse = 4; + /** Connect to the specified network, using previously-defined credentials. */ + command access(invoke: administer) ConnectNetwork(ConnectNetworkRequest): ConnectNetworkResponse = 6; + /** Modify the order in which networks will be presented in the Networks attribute. */ + command access(invoke: administer) ReorderNetwork(ReorderNetworkRequest): NetworkConfigResponse = 8; + /** Retrieve details about and optionally proof of possession of a network client identity. */ + command access(invoke: administer) QueryIdentity(QueryIdentityRequest): QueryIdentityResponse = 9; +} + +/** The General Diagnostics Cluster, along with other diagnostics clusters, provide a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ +cluster GeneralDiagnostics = 51 { + revision 2; + + enum BootReasonEnum : enum8 { + kUnspecified = 0; + kPowerOnReboot = 1; + kBrownOutReset = 2; + kSoftwareWatchdogReset = 3; + kHardwareWatchdogReset = 4; + kSoftwareUpdateCompleted = 5; + kSoftwareReset = 6; + } + + enum HardwareFaultEnum : enum8 { + kUnspecified = 0; + kRadio = 1; + kSensor = 2; + kResettableOverTemp = 3; + kNonResettableOverTemp = 4; + kPowerSource = 5; + kVisualDisplayFault = 6; + kAudioOutputFault = 7; + kUserInterfaceFault = 8; + kNonVolatileMemoryError = 9; + kTamperDetected = 10; + } + + enum InterfaceTypeEnum : enum8 { + kUnspecified = 0; + kWiFi = 1; + kEthernet = 2; + kCellular = 3; + kThread = 4; + } + + enum NetworkFaultEnum : enum8 { + kUnspecified = 0; + kHardwareFailure = 1; + kNetworkJammed = 2; + kConnectionFailed = 3; + } + + enum RadioFaultEnum : enum8 { + kUnspecified = 0; + kWiFiFault = 1; + kCellularFault = 2; + kThreadFault = 3; + kNFCFault = 4; + kBLEFault = 5; + kEthernetFault = 6; + } + + bitmap Feature : bitmap32 { + kDataModelTest = 0x1; + } + + struct NetworkInterface { + char_string<32> name = 0; + boolean isOperational = 1; + nullable boolean offPremiseServicesReachableIPv4 = 2; + nullable boolean offPremiseServicesReachableIPv6 = 3; + octet_string<8> hardwareAddress = 4; + octet_string IPv4Addresses[] = 5; + octet_string IPv6Addresses[] = 6; + InterfaceTypeEnum type = 7; + } + + critical event HardwareFaultChange = 0 { + HardwareFaultEnum current[] = 0; + HardwareFaultEnum previous[] = 1; + } + + critical event RadioFaultChange = 1 { + RadioFaultEnum current[] = 0; + RadioFaultEnum previous[] = 1; + } + + critical event NetworkFaultChange = 2 { + NetworkFaultEnum current[] = 0; + NetworkFaultEnum previous[] = 1; + } + + critical event BootReason = 3 { + BootReasonEnum bootReason = 0; + } + + readonly attribute NetworkInterface networkInterfaces[] = 0; + readonly attribute int16u rebootCount = 1; + readonly attribute optional int64u upTime = 2; + readonly attribute optional int32u totalOperationalHours = 3; + readonly attribute optional BootReasonEnum bootReason = 4; + readonly attribute optional HardwareFaultEnum activeHardwareFaults[] = 5; + readonly attribute optional RadioFaultEnum activeRadioFaults[] = 6; + readonly attribute optional NetworkFaultEnum activeNetworkFaults[] = 7; + readonly attribute boolean testEventTriggersEnabled = 8; + 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 TestEventTriggerRequest { + octet_string<16> enableKey = 0; + int64u eventTrigger = 1; + } + + response struct TimeSnapshotResponse = 2 { + systime_ms systemTimeMs = 0; + nullable posix_ms posixTimeMs = 1; + } + + request struct PayloadTestRequestRequest { + octet_string<16> enableKey = 0; + int8u value = 1; + int16u count = 2; + } + + response struct PayloadTestResponse = 4 { + octet_string payload = 0; + } + + /** Provide a means for certification tests to trigger some test-plan-specific events */ + command access(invoke: manage) TestEventTrigger(TestEventTriggerRequest): DefaultSuccess = 0; + /** Take a snapshot of system time and epoch time. */ + command TimeSnapshot(): TimeSnapshotResponse = 1; + /** Request a variable length payload response. */ + command PayloadTestRequest(PayloadTestRequestRequest): PayloadTestResponse = 3; +} + +/** Commands to trigger a Node to allow a new Administrator to commission it. */ +cluster AdministratorCommissioning = 60 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningWindowStatusEnum : enum8 { + kWindowNotOpen = 0; + kEnhancedWindowOpen = 1; + kBasicWindowOpen = 2; + } + + enum StatusCode : enum8 { + kBusy = 2; + kPAKEParameterError = 3; + kWindowNotOpen = 4; + } + + bitmap Feature : bitmap32 { + kBasic = 0x1; + } + + readonly attribute CommissioningWindowStatusEnum windowStatus = 0; + readonly attribute nullable fabric_idx adminFabricIndex = 1; + readonly attribute nullable vendor_id adminVendorId = 2; + 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 OpenCommissioningWindowRequest { + int16u commissioningTimeout = 0; + octet_string PAKEPasscodeVerifier = 1; + int16u discriminator = 2; + int32u iterations = 3; + octet_string<32> salt = 4; + } + + request struct OpenBasicCommissioningWindowRequest { + int16u commissioningTimeout = 0; + } + + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using enhanced commissioning method. */ + timed command access(invoke: administer) OpenCommissioningWindow(OpenCommissioningWindowRequest): DefaultSuccess = 0; + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using basic commissioning method, if the node supports it. */ + timed command access(invoke: administer) OpenBasicCommissioningWindow(OpenBasicCommissioningWindowRequest): DefaultSuccess = 1; + /** This command is used by a current Administrator to instruct a Node to revoke any active Open Commissioning Window or Open Basic Commissioning Window command. */ + timed command access(invoke: administer) RevokeCommissioning(): DefaultSuccess = 2; +} + +/** This cluster is used to add or remove Operational Credentials on a Commissionee or Node, as well as manage the associated Fabrics. */ +cluster OperationalCredentials = 62 { + revision 1; // NOTE: Default/not specifically set + + enum CertificateChainTypeEnum : enum8 { + kDACCertificate = 1; + kPAICertificate = 2; + } + + enum NodeOperationalCertStatusEnum : enum8 { + kOK = 0; + kInvalidPublicKey = 1; + kInvalidNodeOpId = 2; + kInvalidNOC = 3; + kMissingCsr = 4; + kTableFull = 5; + kInvalidAdminSubject = 6; + kFabricConflict = 9; + kLabelConflict = 10; + kInvalidFabricIndex = 11; + } + + fabric_scoped struct FabricDescriptorStruct { + octet_string<65> rootPublicKey = 1; + vendor_id vendorID = 2; + fabric_id fabricID = 3; + node_id nodeID = 4; + char_string<32> label = 5; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct NOCStruct { + fabric_sensitive octet_string noc = 1; + nullable fabric_sensitive octet_string icac = 2; + fabric_idx fabricIndex = 254; + } + + readonly attribute access(read: administer) NOCStruct NOCs[] = 0; + readonly attribute FabricDescriptorStruct fabrics[] = 1; + readonly attribute int8u supportedFabrics = 2; + readonly attribute int8u commissionedFabrics = 3; + readonly attribute octet_string trustedRootCertificates[] = 4; + readonly attribute int8u currentFabricIndex = 5; + 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 AttestationRequestRequest { + octet_string<32> attestationNonce = 0; + } + + response struct AttestationResponse = 1 { + octet_string<900> attestationElements = 0; + octet_string<64> attestationSignature = 1; + } + + request struct CertificateChainRequestRequest { + CertificateChainTypeEnum certificateType = 0; + } + + response struct CertificateChainResponse = 3 { + octet_string<600> certificate = 0; + } + + request struct CSRRequestRequest { + octet_string<32> CSRNonce = 0; + optional boolean isForUpdateNOC = 1; + } + + response struct CSRResponse = 5 { + octet_string NOCSRElements = 0; + octet_string attestationSignature = 1; + } + + request struct AddNOCRequest { + octet_string<400> NOCValue = 0; + optional octet_string<400> ICACValue = 1; + octet_string<16> IPKValue = 2; + int64u caseAdminSubject = 3; + vendor_id adminVendorId = 4; + } + + request struct UpdateNOCRequest { + octet_string NOCValue = 0; + optional octet_string ICACValue = 1; + } + + response struct NOCResponse = 8 { + NodeOperationalCertStatusEnum statusCode = 0; + optional fabric_idx fabricIndex = 1; + optional char_string<128> debugText = 2; + } + + request struct UpdateFabricLabelRequest { + char_string<32> label = 0; + } + + request struct RemoveFabricRequest { + fabric_idx fabricIndex = 0; + } + + request struct AddTrustedRootCertificateRequest { + octet_string rootCACertificate = 0; + } + + /** Sender is requesting attestation information from the receiver. */ + command access(invoke: administer) AttestationRequest(AttestationRequestRequest): AttestationResponse = 0; + /** Sender is requesting a device attestation certificate from the receiver. */ + command access(invoke: administer) CertificateChainRequest(CertificateChainRequestRequest): CertificateChainResponse = 2; + /** Sender is requesting a certificate signing request (CSR) from the receiver. */ + command access(invoke: administer) CSRRequest(CSRRequestRequest): CSRResponse = 4; + /** Sender is requesting to add the new node operational certificates. */ + command access(invoke: administer) AddNOC(AddNOCRequest): NOCResponse = 6; + /** Sender is requesting to update the node operational certificates. */ + fabric command access(invoke: administer) UpdateNOC(UpdateNOCRequest): NOCResponse = 7; + /** This command SHALL be used by an Administrative Node to set the user-visible Label field for a given Fabric, as reflected by entries in the Fabrics attribute. */ + fabric command access(invoke: administer) UpdateFabricLabel(UpdateFabricLabelRequest): NOCResponse = 9; + /** This command is used by Administrative Nodes to remove a given fabric index and delete all associated fabric-scoped data. */ + command access(invoke: administer) RemoveFabric(RemoveFabricRequest): NOCResponse = 10; + /** This command SHALL add a Trusted Root CA Certificate, provided as its CHIP Certificate representation. */ + command access(invoke: administer) AddTrustedRootCertificate(AddTrustedRootCertificateRequest): DefaultSuccess = 11; +} + +/** The Group Key Management Cluster is the mechanism by which group keys are managed. */ +cluster GroupKeyManagement = 63 { + revision 1; // NOTE: Default/not specifically set + + enum GroupKeySecurityPolicyEnum : enum8 { + kTrustFirst = 0; + kCacheAndSync = 1; + } + + bitmap Feature : bitmap32 { + kCacheAndSync = 0x1; + } + + fabric_scoped struct GroupInfoMapStruct { + group_id groupId = 1; + endpoint_no endpoints[] = 2; + optional char_string<16> groupName = 3; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct GroupKeyMapStruct { + group_id groupId = 1; + int16u groupKeySetID = 2; + fabric_idx fabricIndex = 254; + } + + struct GroupKeySetStruct { + int16u groupKeySetID = 0; + GroupKeySecurityPolicyEnum groupKeySecurityPolicy = 1; + nullable octet_string<16> epochKey0 = 2; + nullable epoch_us epochStartTime0 = 3; + nullable octet_string<16> epochKey1 = 4; + nullable epoch_us epochStartTime1 = 5; + nullable octet_string<16> epochKey2 = 6; + nullable epoch_us epochStartTime2 = 7; + } + + attribute access(write: manage) GroupKeyMapStruct groupKeyMap[] = 0; + readonly attribute GroupInfoMapStruct groupTable[] = 1; + readonly attribute int16u maxGroupsPerFabric = 2; + readonly attribute int16u maxGroupKeysPerFabric = 3; + 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 KeySetWriteRequest { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetReadRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadResponse = 2 { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetRemoveRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadAllIndicesResponse = 5 { + int16u groupKeySetIDs[] = 0; + } + + /** Write a new set of keys for the given key set id. */ + fabric command access(invoke: administer) KeySetWrite(KeySetWriteRequest): DefaultSuccess = 0; + /** Read the keys for a given key set id. */ + fabric command access(invoke: administer) KeySetRead(KeySetReadRequest): KeySetReadResponse = 1; + /** Revoke a Root Key from a Group */ + fabric command access(invoke: administer) KeySetRemove(KeySetRemoveRequest): DefaultSuccess = 3; + /** Return the list of Group Key Sets associated with the accessing fabric */ + fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; +} + +/** This cluster is used to configure a valve. */ +cluster ValveConfigurationAndControl = 129 { + revision 1; + + enum StatusCodeEnum : enum8 { + kFailureDueToFault = 2; + } + + enum ValveStateEnum : enum8 { + kClosed = 0; + kOpen = 1; + kTransitioning = 2; + } + + bitmap Feature : bitmap32 { + kTimeSync = 0x1; + kLevel = 0x2; + } + + bitmap ValveFaultBitmap : bitmap16 { + kGeneralFault = 0x1; + kBlocked = 0x2; + kLeaking = 0x4; + kNotConnected = 0x8; + kShortCircuit = 0x10; + kCurrentExceeded = 0x20; + } + + info event ValveStateChanged = 0 { + ValveStateEnum valveState = 0; + optional percent valveLevel = 1; + } + + info event ValveFault = 1 { + ValveFaultBitmap valveFault = 0; + } + + readonly attribute nullable elapsed_s openDuration = 0; + attribute nullable elapsed_s defaultOpenDuration = 1; + readonly attribute optional nullable epoch_us autoCloseTime = 2; + readonly attribute nullable elapsed_s remainingDuration = 3; + readonly attribute nullable ValveStateEnum currentState = 4; + readonly attribute nullable ValveStateEnum targetState = 5; + readonly attribute optional nullable percent currentLevel = 6; + readonly attribute optional nullable percent targetLevel = 7; + attribute optional percent defaultOpenLevel = 8; + readonly attribute optional ValveFaultBitmap valveFault = 9; + readonly attribute optional int8u levelStep = 10; + 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 OpenRequest { + optional nullable elapsed_s openDuration = 0; + optional percent targetLevel = 1; + } + + /** This command is used to set the valve to its open position. */ + command Open(OpenRequest): DefaultSuccess = 0; + /** This command is used to set the valve to its closed position. */ + command Close(): DefaultSuccess = 1; +} + +endpoint 0 { + device type ma_rootdevice = 22, version 1; + + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster AccessControl { + emits event AccessControlEntryChanged; + emits event AccessControlExtensionChanged; + callback attribute acl; + callback attribute subjectsPerAccessControlEntry; + callback attribute targetsPerAccessControlEntry; + callback attribute accessControlEntriesPerFabric; + callback attribute attributeList; + ram attribute featureMap default = 0; + callback attribute clusterRevision; + } + + server cluster BasicInformation { + emits event StartUp; + emits event ShutDown; + emits event Leave; + callback attribute dataModelRevision; + callback attribute vendorName; + callback attribute vendorID; + callback attribute productName; + callback attribute productID; + persist attribute nodeLabel; + callback attribute location; + callback attribute hardwareVersion; + callback attribute hardwareVersionString; + callback attribute softwareVersion; + callback attribute softwareVersionString; + callback attribute manufacturingDate; + callback attribute partNumber; + callback attribute productURL; + callback attribute productLabel; + callback attribute serialNumber; + persist attribute localConfigDisabled default = 0; + callback attribute uniqueID; + callback attribute capabilityMinima; + callback attribute specificationVersion; + callback attribute maxPathsPerInvoke; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + } + + server cluster GeneralCommissioning { + ram attribute breadcrumb default = 0x0000000000000000; + callback attribute basicCommissioningInfo; + callback attribute regulatoryConfig; + callback attribute locationCapability; + callback attribute supportsConcurrentConnection; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 0x0002; + + handle command ArmFailSafe; + handle command ArmFailSafeResponse; + handle command SetRegulatoryConfig; + handle command SetRegulatoryConfigResponse; + handle command CommissioningComplete; + handle command CommissioningCompleteResponse; + } + + server cluster NetworkCommissioning { + ram attribute maxNetworks; + callback attribute networks; + ram attribute interfaceEnabled; + ram attribute lastNetworkingStatus; + ram attribute lastNetworkID; + ram attribute lastConnectErrorValue; + ram attribute featureMap default = 1; + ram attribute clusterRevision default = 0x0001; + + handle command ScanNetworks; + handle command ScanNetworksResponse; + handle command AddOrUpdateWiFiNetwork; + handle command AddOrUpdateThreadNetwork; + handle command RemoveNetwork; + handle command NetworkConfigResponse; + handle command ConnectNetwork; + handle command ConnectNetworkResponse; + handle command ReorderNetwork; + } + + server cluster GeneralDiagnostics { + emits event BootReason; + callback attribute networkInterfaces; + callback attribute rebootCount; + callback attribute upTime; + callback attribute totalOperationalHours; + callback attribute bootReason; + callback attribute activeHardwareFaults; + callback attribute activeRadioFaults; + callback attribute activeNetworkFaults; + callback attribute testEventTriggersEnabled default = false; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command TestEventTrigger; + handle command TimeSnapshot; + handle command TimeSnapshotResponse; + } + + server cluster AdministratorCommissioning { + callback attribute windowStatus; + callback attribute adminFabricIndex; + callback attribute adminVendorId; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 0x0001; + + handle command OpenCommissioningWindow; + handle command RevokeCommissioning; + } + + server cluster OperationalCredentials { + callback attribute NOCs; + callback attribute fabrics; + callback attribute supportedFabrics; + callback attribute commissionedFabrics; + callback attribute trustedRootCertificates; + callback attribute currentFabricIndex; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 0x0001; + + handle command AttestationRequest; + handle command AttestationResponse; + handle command CertificateChainRequest; + handle command CertificateChainResponse; + handle command CSRRequest; + handle command CSRResponse; + handle command AddNOC; + handle command UpdateNOC; + handle command NOCResponse; + handle command UpdateFabricLabel; + handle command RemoveFabric; + handle command AddTrustedRootCertificate; + } + + server cluster GroupKeyManagement { + callback attribute groupKeyMap; + callback attribute groupTable; + callback attribute maxGroupsPerFabric; + callback attribute maxGroupKeysPerFabric; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command KeySetWrite; + handle command KeySetRead; + handle command KeySetReadResponse; + handle command KeySetRemove; + handle command KeySetReadAllIndices; + handle command KeySetReadAllIndicesResponse; + } +} +endpoint 1 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} +endpoint 2 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} +endpoint 3 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} +endpoint 4 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} +endpoint 5 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} +endpoint 6 { + device type ma_water_valve = 66, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 5; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster ValveConfigurationAndControl { + callback attribute openDuration; + callback attribute defaultOpenDuration; + callback attribute remainingDuration; + callback attribute currentState; + callback attribute targetState; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Open; + handle command Close; + } +} + + diff --git a/examples/valve/valve-common/valve-app.zap b/examples/valve/valve-common/valve-app.zap new file mode 100644 index 00000000000000..1ca7dff0a2b17a --- /dev/null +++ b/examples/valve/valve-common/valve-app.zap @@ -0,0 +1,5277 @@ +{ + "fileFormat": 2, + "featureLevel": 103, + "creator": "zap", + "keyValuePairs": [ + { + "key": "commandDiscovery", + "value": "1" + }, + { + "key": "defaultResponsePolicy", + "value": "always" + }, + { + "key": "manufacturerCodes", + "value": "0x1002" + } + ], + "package": [ + { + "pathRelativity": "relativeToZap", + "path": "../../../src/app/zap-templates/zcl/zcl.json", + "type": "zcl-properties", + "category": "matter", + "version": 1, + "description": "Matter SDK ZCL data" + }, + { + "pathRelativity": "relativeToZap", + "path": "../../../src/app/zap-templates/app-templates.json", + "type": "gen-templates-json", + "category": "matter", + "version": "chip-v1" + } + ], + "endpointTypes": [ + { + "id": 1, + "name": "MA-rootdevice", + "deviceTypeRef": { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice" + }, + "deviceTypes": [ + { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 22 + ], + "deviceTypeName": "MA-rootdevice", + "deviceTypeCode": 22, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Access Control", + "code": 31, + "mfgCode": null, + "define": "ACCESS_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "ACL", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SubjectsPerAccessControlEntry", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetsPerAccessControlEntry", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AccessControlEntriesPerFabric", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "AccessControlEntryChanged", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "AccessControlExtensionChanged", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Basic Information", + "code": 40, + "mfgCode": null, + "define": "BASIC_INFORMATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DataModelRevision", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorName", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorID", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductName", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductID", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "NodeLabel", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "NVM", + "singleton": 1, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "Location", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersion", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersionString", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersion", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersionString", + "code": 10, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ManufacturingDate", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "PartNumber", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductURL", + "code": 13, + "mfgCode": null, + "side": "server", + "type": "long_char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductLabel", + "code": 14, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SerialNumber", + "code": 15, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "LocalConfigDisabled", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "NVM", + "singleton": 1, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UniqueID", + "code": 18, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CapabilityMinima", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "CapabilityMinimaStruct", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SpecificationVersion", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxPathsPerInvoke", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 1, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "StartUp", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "ShutDown", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Leave", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "General Commissioning", + "code": 48, + "mfgCode": null, + "define": "GENERAL_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ArmFailSafe", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ArmFailSafeResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfig", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfigResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CommissioningComplete", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CommissioningCompleteResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "Breadcrumb", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000000000000000", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "BasicCommissioningInfo", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "BasicCommissioningInfo", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RegulatoryConfig", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LocationCapability", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportsConcurrentConnection", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0002", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Network Commissioning", + "code": 49, + "mfgCode": null, + "define": "NETWORK_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ScanNetworks", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ScanNetworksResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddOrUpdateWiFiNetwork", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddOrUpdateThreadNetwork", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveNetwork", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NetworkConfigResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ConnectNetwork", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ConnectNetworkResponse", + "code": 7, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ReorderNetwork", + "code": 8, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "MaxNetworks", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Networks", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "InterfaceEnabled", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkingStatus", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "NetworkCommissioningStatusEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkID", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "octet_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastConnectErrorValue", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int32s", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0001", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "General Diagnostics", + "code": 51, + "mfgCode": null, + "define": "GENERAL_DIAGNOSTICS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "TestEventTrigger", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshot", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshotResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NetworkInterfaces", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RebootCount", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UpTime", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TotalOperationalHours", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BootReason", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "BootReasonEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveHardwareFaults", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveRadioFaults", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveNetworkFaults", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TestEventTriggersEnabled", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "BootReason", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Administrator Commissioning", + "code": 60, + "mfgCode": null, + "define": "ADMINISTRATOR_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "OpenCommissioningWindow", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RevokeCommissioning", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "WindowStatus", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "CommissioningWindowStatusEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminFabricIndex", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "fabric_idx", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminVendorId", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0001", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Operational Credentials", + "code": 62, + "mfgCode": null, + "define": "OPERATIONAL_CREDENTIALS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AttestationRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AttestationResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CertificateChainRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CertificateChainResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CSRRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CSRResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddNOC", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "UpdateNOC", + "code": 7, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NOCResponse", + "code": 8, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "UpdateFabricLabel", + "code": 9, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveFabric", + "code": 10, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddTrustedRootCertificate", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NOCs", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Fabrics", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SupportedFabrics", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CommissionedFabrics", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "TrustedRootCertificates", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CurrentFabricIndex", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0001", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Group Key Management", + "code": 63, + "mfgCode": null, + "define": "GROUP_KEY_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "KeySetWrite", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetRead", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "KeySetRemove", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndices", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndicesResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "GroupKeyMap", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GroupTable", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxGroupsPerFabric", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxGroupKeysPerFabric", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 2, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 3, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 4, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 5, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 6, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 7, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + }, + "deviceTypes": [ + { + "code": 66, + "profileId": 259, + "label": "MA-water-valve", + "name": "MA-water-valve" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 66 + ], + "deviceTypeName": "MA-water-valve", + "deviceTypeCode": 66, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Valve Configuration and Control", + "code": 129, + "mfgCode": null, + "define": "VALVE_CONFIGURATION_AND_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Open", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Close", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OpenDuration", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DefaultOpenDuration", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "RemainingDuration", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetState", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ValveStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + } + ], + "endpoints": [ + { + "endpointTypeName": "MA-rootdevice", + "endpointTypeIndex": 0, + "profileId": 259, + "endpointId": 0, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 1, + "profileId": 259, + "endpointId": 1, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 2, + "profileId": 259, + "endpointId": 2, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 3, + "profileId": 259, + "endpointId": 3, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 4, + "profileId": 259, + "endpointId": 4, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 5, + "profileId": 259, + "endpointId": 5, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 6, + "profileId": 259, + "endpointId": 6, + "networkId": 0, + "parentEndpointIdentifier": null + } + ] +} \ No newline at end of file diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 02be8ebaccf05b..15f5d6472fe047 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -452,6 +452,18 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/ArlEncoder.cpp", "${_app_root}/clusters/${cluster}/ArlEncoder.h", ] + } else if (cluster == "valve-configuration-and-control-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-cluster-logic.cpp", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-cluster-logic.h", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-delegate.h", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-matter-context.cpp", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-matter-context.h", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-server-disco.cpp", + "${_app_root}/clusters/${cluster}/valve-configuration-and-control-server-disco.h", + ] + cflags += [ "-Wno-unused-private-field" ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/cluster-building-blocks/QuieterReporting.h b/src/app/cluster-building-blocks/QuieterReporting.h index e64d56cd2b1c72..1b69a853d0d192 100644 --- a/src/app/cluster-building-blocks/QuieterReporting.h +++ b/src/app/cluster-building-blocks/QuieterReporting.h @@ -205,6 +205,8 @@ class QuieterReportingAttribute return isNewlyDirty ? AttributeDirtyState::kMustReport : AttributeDirtyState::kNoReportNeeded; } + chip::System::Clock::Timeout GetLastReportTime() { return mLastDirtyTimestampMillis; } + /** * Same as the other `SetValue`, but assumes a changedPredicate that never overrides to dirty. * diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.cpp b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.cpp new file mode 100644 index 00000000000000..8bad33e492fbd5 --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.cpp @@ -0,0 +1,578 @@ +/** + * + * 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. + */ +/** + * @file Cross-platform API to handle cluster-specific logic for the valve configuration and control cluster on a single endpoint. + */ + +#include "valve-configuration-and-control-cluster-logic.h" + +#include + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +void ClusterStateAttributes::Init(ClusterInitParameters state) +{ + mState.currentLevel = state.currentLevel; + mState.currentState = state.currentState; + mState.valveFault = state.valveFault; + mState.levelStep = state.levelStep; + + // Overwrite default initial state with stored persisted values if set. + uint32_t defaultOpenDuration; + if (mMatterContext.GetDefaultOpenDuration(defaultOpenDuration) == CHIP_NO_ERROR) + { + mState.defaultOpenDuration.SetNonNull(defaultOpenDuration); + } + mMatterContext.GetDefaultOpenLevel(mState.defaultOpenLevel); + + QuieterReportingPolicyFlags & policy = mState.remainingDuration.policy(); + policy = QuieterReportingPolicyFlags(to_underlying(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero) | + to_underlying(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement)); +} + +CHIP_ERROR ClusterStateAttributes::SetRemainingDuration(const DataModel::Nullable & remainingDuration) +{ + System::Clock::Milliseconds64 now = System::SystemClock().GetMonotonicMilliseconds64(); + AttributeDirtyState dirtyState = mState.remainingDuration.SetValue( + remainingDuration, now, mState.remainingDuration.GetPredicateForSufficientTimeSinceLastDirty(kRemainingDurationReportRate)); + if (dirtyState == AttributeDirtyState::kMustReport) + { + mMatterContext.MarkDirty(Attributes::RemainingDuration::Id); + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterStateAttributes::SetOpenDuration(const DataModel::Nullable & openDuration) +{ + bool dirty = openDuration != mState.openDuration; + mState.openDuration = openDuration; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::OpenDuration::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetDefaultOpenDuration(const DataModel::Nullable & defaultOpenDuration) +{ + bool dirty = defaultOpenDuration != mState.defaultOpenDuration; + ReturnErrorOnFailure(mMatterContext.StoreDefaultOpenDuration(defaultOpenDuration)); + mState.defaultOpenDuration = defaultOpenDuration; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::DefaultOpenDuration::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetAutoCloseTime(const DataModel::Nullable & autoCloseTime) +{ + bool dirty = autoCloseTime != mState.autoCloseTime; + mState.autoCloseTime = autoCloseTime; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::AutoCloseTime::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetCurrentState(const DataModel::Nullable & currentState) +{ + bool dirty = currentState != mState.currentState; + mState.currentState = currentState; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::CurrentState::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetTargetState(const DataModel::Nullable & targetState) +{ + bool dirty = targetState != mState.targetState; + mState.targetState = targetState; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::TargetState::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetCurrentLevel(const DataModel::Nullable & currentLevel) +{ + // TODO: Q quality + bool dirty = currentLevel != mState.currentLevel; + mState.currentLevel = currentLevel; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::CurrentLevel::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetTargetLevel(const DataModel::Nullable & targetLevel) +{ + bool dirty = targetLevel != mState.targetLevel; + mState.targetLevel = targetLevel; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::TargetLevel::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetDefaultOpenLevel(const Percent defaultOpenLevel) +{ + bool dirty = defaultOpenLevel != mState.defaultOpenLevel; + ReturnErrorOnFailure(mMatterContext.StoreDefaultOpenLevel(defaultOpenLevel)); + mState.defaultOpenLevel = defaultOpenLevel; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::DefaultOpenLevel::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetValveFault(const BitMask & valveFault) +{ + bool dirty = valveFault != mState.valveFault; + mState.valveFault = valveFault; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::ValveFault::Id); + } + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterStateAttributes::SetLevelStep(const uint8_t levelStep) +{ + bool dirty = levelStep != mState.levelStep; + mState.levelStep = levelStep; + if (dirty) + { + mMatterContext.MarkDirty(Attributes::LevelStep::Id); + } + return CHIP_NO_ERROR; +} + +System::Clock::Milliseconds64 ClusterStateAttributes::GetNextReportTimeForRemainingDuration() +{ + return std::chrono::duration_cast(mState.remainingDuration.GetLastReportTime()) + + kRemainingDurationReportRate; +} + +CHIP_ERROR ClusterLogic::Init(const ClusterConformance & conformance, const ClusterInitParameters & initialState) +{ + if (!conformance.Valid()) + { + return CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR; + } + if (conformance.HasFeature(Feature::kLevel) && mClusterDriver.GetDelegateType() != kLevel) + { + return CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR; + } + if (!conformance.HasFeature(Feature::kLevel) && mClusterDriver.GetDelegateType() != kNonLevel) + { + return CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR; + } + mConformance = conformance; + + mState.Init(initialState); + mDurationStarted = System::SystemClock().GetMonotonicMilliseconds64(); + + mInitialized = true; + return CHIP_NO_ERROR; +} + +// All Get functions: +// Return CHIP_ERROR_INVALID_STATE if the class has not been initialized. +// Return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the attribute is not supported by the conformance. +// Return CHIP_NO_ERROR and set the parameter value otherwise +CHIP_ERROR ClusterLogic::GetOpenDuration(DataModel::Nullable & openDuration) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + openDuration = mState.GetState().openDuration; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetDefaultOpenDuration(DataModel::Nullable & defaultOpenDuration) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + defaultOpenDuration = mState.GetState().defaultOpenDuration; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetAutoCloseTime(DataModel::Nullable & autoCloseTime) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.HasFeature(Feature::kTimeSync), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + autoCloseTime = mState.GetState().autoCloseTime; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetRemainingDuration(DataModel::Nullable & remainingDuration) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + HandleUpdateRemainingDurationInternal(); + remainingDuration = mState.GetState().remainingDuration.value(); + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetCurrentState(DataModel::Nullable & currentState) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + currentState = mState.GetState().currentState; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetTargetState(DataModel::Nullable & targetState) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + targetState = mState.GetState().targetState; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetCurrentLevel(DataModel::Nullable & currentLevel) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + currentLevel = mState.GetState().currentLevel; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetTargetLevel(DataModel::Nullable & targetLevel) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + targetLevel = mState.GetState().targetLevel; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetDefaultOpenLevel(uint8_t & defaultOpenLevel) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + VerifyOrReturnError(mConformance.supportsDefaultOpenLevel, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + defaultOpenLevel = mState.GetState().defaultOpenLevel; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetValveFault(BitMask & valveFault) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.supportsValveFault, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + valveFault = mState.GetState().valveFault; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetLevelStep(uint8_t & levelStep) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + VerifyOrReturnError(mConformance.supportsLevelStep, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + levelStep = mState.GetState().levelStep; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetFeatureMap(Attributes::FeatureMap::TypeInfo::Type & featureMap) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + featureMap = mConformance.featureMap; + return CHIP_NO_ERROR; +} +CHIP_ERROR ClusterLogic::GetClusterRevision(Attributes::ClusterRevision::TypeInfo::Type & clusterRevision) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + clusterRevision = kClusterRevision; + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterLogic::SetDefaultOpenDuration(const DataModel::Nullable & defaultOpenDuration) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + if (!defaultOpenDuration.IsNull() && defaultOpenDuration.Value() < 1) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + return mState.SetDefaultOpenDuration(defaultOpenDuration); +} + +bool ClusterLogic::ValueCompliesWithLevelStep(const uint8_t value) +{ + if (mConformance.supportsLevelStep) + { + if ((value != 100u) && ((value % mState.GetState().levelStep) != 0)) + { + return false; + } + } + return true; +} + +CHIP_ERROR ClusterLogic::SetDefaultOpenLevel(const uint8_t defaultOpenLevel) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mConformance.supportsDefaultOpenLevel, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + if (defaultOpenLevel < 1 || defaultOpenLevel > 100) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + VerifyOrReturnError(ValueCompliesWithLevelStep(defaultOpenLevel), CHIP_ERROR_INVALID_ARGUMENT); + return mState.SetDefaultOpenLevel(defaultOpenLevel); +} + +CHIP_ERROR ClusterLogic::SetValveFault(const ValveFaultBitmap valveFault) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} +CHIP_ERROR ClusterLogic::ClearValveFault(const ValveFaultBitmap valveFault) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ClusterLogic::GetRealTargetLevel(const std::optional & targetLevel, Percent & realTargetLevel) +{ + if (!targetLevel.has_value()) + { + if (mConformance.supportsDefaultOpenLevel) + { + realTargetLevel = mState.GetState().defaultOpenLevel; + return CHIP_NO_ERROR; + } + realTargetLevel = 100u; + return CHIP_NO_ERROR; + } + // targetLevel has a value + VerifyOrReturnError(ValueCompliesWithLevelStep(targetLevel.value()), CHIP_ERROR_INVALID_ARGUMENT); + realTargetLevel = targetLevel.value(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterLogic::HandleOpenLevel(const std::optional & targetLevel) +{ + // This function should only be called for devices that support the level feature. + VerifyOrReturnError(mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_INTERNAL); + + Percent realTargetLevel; + Percent returnedCurrentLevel = 0; + BitMask returnedValveFault = 0; + ReturnErrorOnFailure(GetRealTargetLevel(targetLevel, realTargetLevel)); + + CHIP_ERROR err = mClusterDriver.HandleOpenValve(realTargetLevel, returnedCurrentLevel, returnedValveFault); + + if (mConformance.supportsValveFault) + { + mState.SetValveFault(returnedValveFault); + } + if (err != CHIP_NO_ERROR) + { + return err; + } + + mState.SetTargetLevel(realTargetLevel); + mState.SetCurrentLevel(returnedCurrentLevel); + mState.SetTargetState(DataModel::Nullable(ValveStateEnum::kOpen)); + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kTransitioning)); + + if (returnedCurrentLevel == realTargetLevel) + { + mState.SetTargetLevel(DataModel::NullNullable); + mState.SetCurrentLevel(realTargetLevel); + mState.SetTargetState(DataModel::NullNullable); + mState.SetCurrentState(ValveStateEnum::kOpen); + } + else + { + // TODO: Need to start a timer to continue querying the device for updates. Or just let the delegate handle this? + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterLogic::HandleOpenNoLevel() +{ + // This function should only be called for devices that do not support the level feature. + VerifyOrReturnError(!mConformance.HasFeature(Feature::kLevel), CHIP_ERROR_INTERNAL); + + ValveStateEnum returnedState = ValveStateEnum::kUnknownEnumValue; + BitMask returnedValveFault = 0; + + // Per the spec, set these to transitioning regardless + mState.SetTargetState(DataModel::Nullable(ValveStateEnum::kOpen)); + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kTransitioning)); + + CHIP_ERROR err = mClusterDriver.HandleOpenValve(returnedState, returnedValveFault); + if (mConformance.supportsValveFault) + { + mState.SetValveFault(returnedValveFault); + } + if (err != CHIP_NO_ERROR) + { + // TODO: How should the target and current be set in this case? + return err; + } + + if (returnedState == ValveStateEnum::kOpen) + { + mState.SetTargetState(DataModel::NullNullable); + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kOpen)); + } + else + { + // TODO: Need to start a timer to continue querying the device for updates. Or just let the delegate handle this? + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterLogic::HandleOpenCommand(std::optional> openDuration, + std::optional targetLevel) +{ + // openDuration + // - if this is omitted, fall back to defaultOpenDuration + // - if this is NULL, remaining duration is NULL + // - if this is a value, use that value + // - if remaining duration is not null and TS is supported, set the autoCloseTime as appropriate + // targetLevel + // - if LVL is not supported + // - if this is omitted, that's correct + // - if this is supplied return error + // - if LVL is supported + // - if this value is not supplied, use defaultOpenLevel if supported, otherwise 100 + // - if this value is supplied, check against levelStep, error if not OK, otherwise set targetLevel + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + + DataModel::Nullable realOpenDuration; + if (openDuration.has_value()) + { + realOpenDuration = openDuration.value(); + } + else + { + realOpenDuration = mState.GetState().defaultOpenDuration; + } + + if (!mConformance.HasFeature(Feature::kLevel) && targetLevel.has_value()) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (mConformance.HasFeature(Feature::kLevel)) + { + ReturnErrorOnFailure(HandleOpenLevel(targetLevel)); + } + else + { + ReturnErrorOnFailure(HandleOpenNoLevel()); + } + + mState.SetOpenDuration(realOpenDuration); + mDurationStarted = System::SystemClock().GetMonotonicMilliseconds64(); + HandleUpdateRemainingDurationInternal(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClusterLogic::HandleCloseCommand() +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + DeviceLayer::SystemLayer().CancelTimer(HandleUpdateRemainingDuration, this); + return HandleCloseInternal(); +} + +CHIP_ERROR ClusterLogic::HandleCloseInternal() +{ + CHIP_ERROR err; + BitMask faults; + if (mConformance.HasFeature(Feature::kLevel)) + { + Percent currentLevel; + mState.SetTargetLevel(0); + mState.SetTargetState(DataModel::Nullable(ValveStateEnum::kClosed)); + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kTransitioning)); + err = mClusterDriver.HandleCloseValve(currentLevel, faults); + if (err == CHIP_NO_ERROR) + { + mState.SetCurrentLevel(DataModel::Nullable(currentLevel)); + if (currentLevel == 0) + { + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kClosed)); + mState.SetTargetState(DataModel::NullNullable); + mState.SetTargetLevel(DataModel::NullNullable); + } + else + { + // TODO: start a timer here to query the delegate? + } + } + } + else + { + ValveStateEnum state; + mState.SetTargetState(DataModel::Nullable(ValveStateEnum::kClosed)); + mState.SetCurrentState(DataModel::Nullable(ValveStateEnum::kTransitioning)); + err = mClusterDriver.HandleCloseValve(state, faults); + if (err == CHIP_NO_ERROR) + { + mState.SetCurrentState(state); + } + } + // If there was an error, we know nothing about the current state + if (err != CHIP_NO_ERROR) + { + mState.SetCurrentLevel(DataModel::NullNullable); + mState.SetCurrentState(DataModel::NullNullable); + } + mState.SetValveFault(faults); + mState.SetOpenDuration(DataModel::NullNullable); + mState.SetRemainingDuration(DataModel::NullNullable); + mState.SetTargetLevel(DataModel::NullNullable); + mState.SetTargetState(DataModel::NullNullable); + mState.SetAutoCloseTime(DataModel::NullNullable); + return err; +} + +void ClusterLogic::HandleUpdateRemainingDuration(System::Layer * systemLayer, void * context) +{ + auto * logic = static_cast(context); + logic->HandleUpdateRemainingDurationInternal(); +} + +void ClusterLogic::HandleUpdateRemainingDurationInternal() +{ + // Start by cancelling the timer in case this was called from a command handler + // We will start a new timer if required. + DeviceLayer::SystemLayer().CancelTimer(HandleUpdateRemainingDuration, this); + + if (mState.GetState().openDuration.IsNull()) + { + // I think this might be an error state - if openDuration is NULL, this timer shouldn't be on. + mState.SetRemainingDuration(DataModel::NullNullable); + return; + } + + // Setup a new timer to either send the next report or handle the close operation + System::Clock::Milliseconds64 now = System::SystemClock().GetMonotonicMilliseconds64(); + System::Clock::Seconds64 openDurationS = System::Clock::Seconds64(mState.GetState().openDuration.ValueOr(0)); + System::Clock::Milliseconds64 closeTimeMs = + mDurationStarted + std::chrono::duration_cast(openDurationS); + if (now >= closeTimeMs) + { + // Time's up, close the valve. Close handles setting the open and remaining duration + HandleCloseInternal(); + return; + } + System::Clock::Milliseconds64 remainingMs = closeTimeMs - now; + System::Clock::Milliseconds64 nextReportTimer = mState.GetNextReportTimeForRemainingDuration() - now; + + System::Clock::Milliseconds64 nextTimerTime = std::min(nextReportTimer, remainingMs); + DeviceLayer::SystemLayer().StartTimer(std::chrono::duration_cast(nextTimerTime), + HandleUpdateRemainingDuration, this); + + auto remainingS = std::chrono::round(remainingMs); + mState.SetRemainingDuration(DataModel::Nullable(remainingS.count())); +} + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.h b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.h new file mode 100644 index 00000000000000..dd1aacd1d7d38b --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.h @@ -0,0 +1,221 @@ +/** + * + * 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. + */ +/** + * @file Cross-platform API to handle cluster-specific logic for the valve configuration and control cluster on a single endpoint. + */ + +#pragma once + +#include "valve-configuration-and-control-delegate.h" +#include "valve-configuration-and-control-matter-context.h" +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +struct ClusterConformance +{ + inline bool HasFeature(Feature feature) const { return featureMap & to_underlying(feature); } + uint32_t featureMap; + bool supportsDefaultOpenLevel; + bool supportsValveFault; + bool supportsLevelStep; + bool Valid() const + { + bool supportsLvl = HasFeature(Feature::kLevel); + if (supportsDefaultOpenLevel & !supportsLvl) + { + ChipLogError(Zcl, + "Invalid Valve configuration and control conformance - DefaultOpenLevel is not supported without LVL"); + return false; + } + if (supportsLevelStep & !supportsLvl) + { + ChipLogError(Zcl, "Invalid Valve configuration and control conformance - LevelStep is not supported without LVL"); + return false; + } + return true; + } +}; + +struct ClusterInitParameters +{ + DataModel::Nullable currentState = DataModel::NullNullable; + DataModel::Nullable currentLevel = DataModel::NullNullable; + BitMask valveFault = 0u; + uint8_t levelStep = 1u; +}; +struct ClusterState +{ + DataModel::Nullable openDuration = DataModel::NullNullable; + DataModel::Nullable defaultOpenDuration = DataModel::NullNullable; + DataModel::Nullable autoCloseTime = DataModel::NullNullable; + QuieterReportingAttribute remainingDuration = QuieterReportingAttribute(); + DataModel::Nullable currentState = DataModel::NullNullable; + DataModel::Nullable targetState = DataModel::NullNullable; + DataModel::Nullable currentLevel = DataModel::NullNullable; + DataModel::Nullable targetLevel = DataModel::NullNullable; + Percent defaultOpenLevel = 100u; + BitMask valveFault = 0u; + uint8_t levelStep = 1u; +}; + +// Attribute sets are forced to go through this class so the attributes can be marked dirty appropriately. +// This cluster handles storage and marking dirty. The cluster logic should handle constraint and conformance checking. +class ClusterStateAttributes +{ +public: + explicit ClusterStateAttributes(MatterContext & matterContext) : mMatterContext(matterContext) {}; + void Init(ClusterInitParameters initialState); + const ClusterState & GetState() { return mState; } + + CHIP_ERROR SetOpenDuration(const DataModel::Nullable & openDuration); + CHIP_ERROR SetDefaultOpenDuration(const DataModel::Nullable & defaultOpenDuration); + CHIP_ERROR SetAutoCloseTime(const DataModel::Nullable & autoCloseTime); + CHIP_ERROR SetRemainingDuration(const DataModel::Nullable & remainingDuration); + CHIP_ERROR SetCurrentState(const DataModel::Nullable & currentState); + CHIP_ERROR SetTargetState(const DataModel::Nullable & targetState); + CHIP_ERROR SetCurrentLevel(const DataModel::Nullable & currentLevel); + CHIP_ERROR SetTargetLevel(const DataModel::Nullable & targetLevel); + CHIP_ERROR SetDefaultOpenLevel(const Percent defaultOpenLevel); + CHIP_ERROR SetValveFault(const BitMask & valveFault); + CHIP_ERROR SetLevelStep(const uint8_t levelStep); + + System::Clock::Milliseconds64 GetNextReportTimeForRemainingDuration(); + +private: + const System::Clock::Milliseconds64 kRemainingDurationReportRate = + std::chrono::duration_cast(System::Clock::Seconds64(1)); + ClusterState mState; + MatterContext & mMatterContext; +}; + +class ClusterLogic +{ +public: + // Instantiates a ClusterLogic class. The caller maintains ownership of the driver and the context, but provides them for use by + // the ClusterLogic class. + ClusterLogic(DelegateBase & clusterDriver, MatterContext & matterContext) : + mClusterDriver(clusterDriver), mState(ClusterStateAttributes(matterContext)) + { + // TODO: remove these once the fields are used properly + (void) mClusterDriver; + } + + // Validates the conformance and performs initialization. + // Returns CHIP_ERROR_INCORRECT_STATE if the cluster has already been initialized. + // Returns CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR if the conformance is incorrect. + CHIP_ERROR Init(const ClusterConformance & conformance, const ClusterInitParameters & initialState = ClusterInitParameters()); + // CHIP_ERROR HandleOpen(); + // CHIP_ERROR HandleClose(); + + // All Get functions: + // Return CHIP_ERROR_INCORRECT_STATE if the class has not been initialized. + // Return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the attribute is not supported by the conformance. + // Otherwise return CHIP_NO_ERROR and set the input parameter value to the current cluster state value + CHIP_ERROR GetOpenDuration(DataModel::Nullable & openDuration); + CHIP_ERROR GetDefaultOpenDuration(DataModel::Nullable & defaultOpenDuration); + CHIP_ERROR GetAutoCloseTime(DataModel::Nullable & autoCloseTime); + CHIP_ERROR GetRemainingDuration(DataModel::Nullable & remainingDuration); + CHIP_ERROR GetCurrentState(DataModel::Nullable & currentState); + CHIP_ERROR GetTargetState(DataModel::Nullable & targetState); + CHIP_ERROR GetCurrentLevel(DataModel::Nullable & currentLevel); + CHIP_ERROR GetTargetLevel(DataModel::Nullable & targetLevel); + CHIP_ERROR GetDefaultOpenLevel(Percent & defaultOpenLevel); + CHIP_ERROR GetValveFault(BitMask & valveFault); + CHIP_ERROR GetLevelStep(uint8_t & levelStep); + CHIP_ERROR GetFeatureMap(Attributes::FeatureMap::TypeInfo::Type & featureMap); + CHIP_ERROR GetClusterRevision(Attributes::ClusterRevision::TypeInfo::Type & clusterRevision); + + // All Set functions + // Return CHIP_ERROR_INCORRECT_STATE if the class has not been initialized. + // Return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if the attribute is not supported by the conformance. + // Return CHIP_ERROR_INVALID_ARGUMENT if the input value is out of range. + // Returns CHIP_ERROR_PERSISTED_STORAGE_FAILED if the value could not not be stored in persistent storage. + // Otherwise return CHIP_NO_ERROR and set the parameter value in the cluster state + // Set functions are supplied for any values that can be set either internally by the device or externally + // through a direct attribute write. Changes to attributes that happen as a side effect of cluster commands + // are handled by the cluster command handlers. + + // DefaultOpenDuration can be set by the client using attribute write + CHIP_ERROR SetDefaultOpenDuration(const DataModel::Nullable & defaultOpenDuration); + + // DefaultOpenLevel can be set by the client using attribute write + CHIP_ERROR SetDefaultOpenLevel(const uint8_t defaultOpenLevel); + + // ValveFault can be set internally by the device. + // Use the Set function to add a specific valve fault and the clear function to clear it. + // Q: Should we push these through the delegate? + CHIP_ERROR SetValveFault(const ValveFaultBitmap valveFault); + CHIP_ERROR ClearValveFault(const ValveFaultBitmap valveFault); + + // Other ones that are set internally? + // Current state + // Current level + + // Returns CHIP_ERROR_INCORRECT_STATE if the class has not been initialized. + // Returns CHIP_ERROR_INVALID_ARGUMENT if the input values are out is out of range or the targetLevel is supplied when LVL is + // not supported. + // Calls delegate HandleOpen function after validating the parameters + CHIP_ERROR HandleOpenCommand(std::optional> openDuration, std::optional targetLevel); + + // Returns CHIP_ERROR_INCORRECT_STATE if the class has not been initialized. + // Calls delegate HandleClose function after validating the parameters and stops any open duration timers. + CHIP_ERROR HandleCloseCommand(); + +private: + // This cluster implements version 1 of the valve cluster. Do not change this revision without updating + // the cluster to implement the newest features. + // TODO: consider implementing the server such that multiple revisions can be supported + static constexpr Attributes::ClusterRevision::TypeInfo::Type kClusterRevision = 1u; + // Determines if the level value is allowed per the level step. + bool ValueCompliesWithLevelStep(const uint8_t value); + // Returns the target level to send to the delegate based on the targetLevel command field, the device conformance and the + // defaults. Returns error if the supplied target level is invalid. + CHIP_ERROR GetRealTargetLevel(const std::optional & targetLevel, Percent & realTargetLevel); + // Internal function call to handle open commands for devices that support the LVL feature. + CHIP_ERROR HandleOpenLevel(const std::optional & targetLevel); + // Internal function call to handle open commands for devices that do not support the LVL feature. + CHIP_ERROR HandleOpenNoLevel(); + + // Wrapper for the timer function. Private member so it can call private functions. + static void HandleUpdateRemainingDuration(System::Layer * systemLayer, void * context); + // internal function called by wrapper + void HandleUpdateRemainingDurationInternal(); + // Internal function called by HandleUpdateRemainingDuration to call the close function in the delegate and + // set all the attributes back to their closed state. + CHIP_ERROR HandleCloseInternal(); + + bool mInitialized = false; + System::Clock::Milliseconds64 mDurationStarted = System::Clock::Milliseconds64(0); + + DelegateBase & mClusterDriver; + + ClusterConformance mConformance; + ClusterStateAttributes mState; +}; + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-delegate.h b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-delegate.h index b0d3e5fd14a90d..30ddc40f7cb4d2 100644 --- a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-delegate.h +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-delegate.h @@ -31,6 +31,15 @@ namespace ValveConfigurationAndControl { /** @brief * Defines methods for implementing application-specific logic for the Valve Configuration and Control Cluster. */ + +// TODO: Change this to a level delegate and a non-level delegate. +enum DelegateType +{ + kBase, + kLevel, + kNonLevel, +}; + class Delegate { public: @@ -44,6 +53,128 @@ class Delegate virtual ~Delegate() = default; }; +class DelegateBase +{ +public: + DelegateBase(){}; + virtual ~DelegateBase() = default; + + // This delegate function will be called only for valve implementations that support the LVL feature. + // Delegates for valves that do not support the LVL feature return CHIP_ERROR_NOT_IMPLEMENTED. + // When this function is called, the delegate should set the valve to the target level, or begin the async process of opening + // the valve to the desired level. + // If the valve is able to be opened (success) + // - the delegate should set currentLevel + // - If the valve is fully open to the target level, currentLevel should be set to the targetLevel + // - If the valve is not fully opened, the delegate should set the currentLevel to the current valve level and the caller + // will continue to query the valve level until the target level is reached or the valve is closed. + // - A valve fault may be returned even if the Open command is successful, if the fault did not prevent the valve from safely + // opening + // - return CHIP_NO_ERROR + // If the valve cannot be safely opened (failure) + // - The delegate should set the valveFault parameter to indicate the reason for the failure (if applicable) + // - The delegate should return a CHIP_ERROR_INTERNAL + virtual CHIP_ERROR HandleOpenValve(const Percent targetLevel, Percent & currentLevel, + BitMask & valveFault) = 0; + + // This delegate function will be called only for valve implementations that DO NOT support the LVL feature. + // Delegates for valves that support the LVL feature should return CHIP_ERROR_NOT_IMPLEMENTED. + // When this function is called, the delegate should open the valve, or begin the async process of opening the valve. + // If the valve is able to be opened (success) + // - If the valve is fully open, currentState should be set to kOpen + // - If the valve is not fully opened, the delegate should set the currentState to kTransitioning and the caller + // will continue to query the valve level until the target level is reached or the valve is closed. + // - A valve fault may be returned even if the Open command is successful, if the fault did not prevent the valve from safely + // opening + // - return CHIP_NO_ERROR + // If the valve cannot be safely opened (failure) + // - The delegate should set the valveFault parameter to indicate the reason for the failure (if applicable) + // - The delegate should return a CHIP_ERROR_INTERNAL + virtual CHIP_ERROR HandleOpenValve(ValveStateEnum & currentState, BitMask & valveFault) = 0; + + // This delegate function will be called only for valve implementations that support the LVL feature. + virtual Percent GetCurrentValveLevel() = 0; + // This delegate function will be called only for valve implementations that do not support the LVL feature. + virtual ValveStateEnum GetCurrentValveState() = 0; + + // This delegate function will be called when the valve needs to be closed either due to an explicit command or + // from the expiration of the open duration. This function will be called for valves that support the LVL feature. + // Delegates for valves that do not support the LVL feature should return CHIP_ERROR_NOT_IMPLEMENTED. + // When this function is called, the delegate should close the valve, or begin the async process of closing. + // If the valve is able to be closed (success) + // - the delegate should set currentLevel + // - If the valve is fully closed, currentLevel should be set to 0 + // - If the valve is not fully closed, the delegate should set the currentLevel to the current valve level and the caller + // will continue to query the valve level until the valve reaches 0 or a command overrides. + // - A valve fault may be returned even if the Open command is successful, if the fault did not prevent the valve from safely + // closing + // - return CHIP_NO_ERROR + // If the valve cannot be closed (failure) + // - The delegate should set the valveFault parameter to indicate the reason for the failure (if applicable) + // - The delegate should return a CHIP_ERROR_INTERNAL + virtual CHIP_ERROR HandleCloseValve(Percent & currentLevel, BitMask & valveFault) = 0; + + // This delegate function will be called when the valve needs to be closed either due to an explicit command or + // from the expiration of the open duration. + // This delegate function will be called only for valve implementations that DO NOT support the LVL feature. + // Delegates for valves that support the LVL feature should return CHIP_ERROR_NOT_IMPLEMENTED. + // When this function is called, the delegate should close the valve, or begin the async process of closing. + // If the valve is able to be closed (success) + // - If the valve is fully closed, currentState should be set to kClosed + // - If the valve is not fully opened, the delegate should set the currentLevel to the current valve level and the caller + // will continue to query the valve level until the target level is reached or the valve is closed. + // - A valve fault may be returned even if the Open command is successful, if the fault did not prevent the valve from safely + // opening + // - return CHIP_NO_ERROR + // If the valve cannot be safely opened (failure) + // - The delegate should set the valveFault parameter to indicate the reason for the failure (if applicable) + // - The delegate should return a CHIP_ERROR_INTERNAL + virtual CHIP_ERROR HandleCloseValve(ValveStateEnum & currentState, BitMask & valveFault) = 0; + + virtual DelegateType GetDelegateType() { return DelegateType::kBase; }; +}; + +class LevelControlDelegate : public DelegateBase +{ + virtual CHIP_ERROR HandleOpenValve(const Percent targetLevel, Percent & currentLevel, + BitMask & valveFault) = 0; + + virtual Percent GetCurrentValveLevel() = 0; + virtual CHIP_ERROR HandleCloseValve(Percent & currentLevel, BitMask & valveFault) = 0; + + // Final overrides - the driver should not implement these classes + CHIP_ERROR HandleOpenValve(ValveStateEnum & currentState, BitMask & valveFault) final + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR HandleCloseValve(ValveStateEnum & currentState, BitMask & valveFault) final + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + ValveStateEnum GetCurrentValveState() final { return ValveStateEnum::kUnknownEnumValue; } + DelegateType GetDelegateType() final { return DelegateType::kLevel; }; +}; + +class NonLevelControlDelegate : public DelegateBase +{ + virtual CHIP_ERROR HandleOpenValve(ValveStateEnum & currentState, BitMask & valveFault) = 0; + virtual ValveStateEnum GetCurrentValveState() = 0; + virtual CHIP_ERROR HandleCloseValve(ValveStateEnum & currentState, BitMask & valveFault) = 0; + + CHIP_ERROR HandleOpenValve(const Percent targetLevel, Percent & currentLevel, BitMask & valveFault) final + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + CHIP_ERROR HandleCloseValve(Percent & currentLevel, BitMask & valveFault) final + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + Percent GetCurrentValveLevel() final { return 0; } + DelegateType GetDelegateType() final { return DelegateType::kNonLevel; }; +}; + } // namespace ValveConfigurationAndControl } // namespace Clusters } // namespace app diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.cpp b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.cpp new file mode 100644 index 00000000000000..cb6ef4b7f84613 --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.cpp @@ -0,0 +1,72 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "valve-configuration-and-control-matter-context.h" + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +CHIP_ERROR MatterContext::StoreDefaultOpenDuration(const DataModel::Nullable & defaultOpenDuration) +{ + + if (defaultOpenDuration.IsNull()) + { + return mPersistentStorageDelegate.SyncDeleteKeyValue( + DefaultStorageKeyAllocator::VCCDefaultOpenDuration(mEndpoint).KeyName()); + } + uint32_t val = defaultOpenDuration.Value(); + return mPersistentStorageDelegate.SyncSetKeyValue(DefaultStorageKeyAllocator::VCCDefaultOpenDuration(mEndpoint).KeyName(), &val, + sizeof(val)); +} + +CHIP_ERROR MatterContext::GetDefaultOpenDuration(uint32_t & returnVal) +{ + uint16_t size = static_cast(sizeof(returnVal)); + return mPersistentStorageDelegate.SyncGetKeyValue(DefaultStorageKeyAllocator::VCCDefaultOpenDuration(mEndpoint).KeyName(), + &returnVal, size); +} + +CHIP_ERROR MatterContext::StoreDefaultOpenLevel(const uint8_t defaultOpenLevel) +{ + + return mPersistentStorageDelegate.SyncSetKeyValue(DefaultStorageKeyAllocator::VCCDefaultOpenLevel(mEndpoint).KeyName(), + &defaultOpenLevel, sizeof(defaultOpenLevel)); +} + +CHIP_ERROR MatterContext::GetDefaultOpenLevel(uint8_t & returnVal) +{ + uint16_t size = static_cast(sizeof(returnVal)); + return mPersistentStorageDelegate.SyncGetKeyValue(DefaultStorageKeyAllocator::VCCDefaultOpenLevel(mEndpoint).KeyName(), + &returnVal, size); +} + +void MatterContext::MarkDirty(const AttributeId attributeId) +{ + MatterReportingAttributeChangeCallback(mEndpoint, Id, attributeId); +} + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.h b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.h new file mode 100644 index 00000000000000..93707b3b85d4cd --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.h @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +/** @brief + * Interface to allow interaction with interaction model and ember layers. Can be faked for unit testing. + */ +class MatterContext +{ +public: + // TODO: remove touch on mEndpoint once this is used. I am apparently unable to locate the proper place to turn this off in the + // build file, so whatever, compiler, you win. I've touched it. You happy now? + MatterContext(EndpointId endpoint, PersistentStorageDelegate & persistentStorageDelegate) : + mEndpoint(endpoint), mPersistentStorageDelegate(persistentStorageDelegate) + {} + + // All Set functions: + // Return CHIP_ERROR_PERSISTED_STORAGE_FAILED if the value could not be stored. + // Return CHIP_NO_ERROR if the value was successfully stored. Clear the storage on a NullNullable. + CHIP_ERROR StoreDefaultOpenDuration(const DataModel::Nullable & val); + CHIP_ERROR StoreDefaultOpenLevel(const uint8_t val); + + // All Get functions + // Return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if there is no value in storage. + // Return CHIP_NO_ERROR and fill return value if the value is found. + CHIP_ERROR GetDefaultOpenDuration(uint32_t & returnVal); + CHIP_ERROR GetDefaultOpenLevel(uint8_t & returnVal); + + // MarkDirty + virtual void MarkDirty(AttributeId attributeId); + + virtual ~MatterContext() = default; + +private: + EndpointId mEndpoint; + PersistentStorageDelegate & mPersistentStorageDelegate; +}; + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.cpp b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.cpp new file mode 100644 index 00000000000000..ad105c43714329 --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.cpp @@ -0,0 +1,218 @@ +/** + * + * Copyright (c) 2024 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 "valve-configuration-and-control-server-disco.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +using namespace Attributes; +using namespace Commands; +using namespace Protocols::InteractionModel; +namespace { + +CHIP_ERROR TranslateErrorToIMStatus(CHIP_ERROR err) +{ + if (err == CHIP_NO_ERROR) + { + return err; + } + if (err == CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + if (err == CHIP_ERROR_INCORRECT_STATE) + { + // what actually gets returned here? This is really an internal error, so failure seems perhaps correct. + return CHIP_IM_GLOBAL_STATUS(Failure); + } + if (err == CHIP_ERROR_INVALID_ARGUMENT) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + // Catch-all error + return CHIP_IM_GLOBAL_STATUS(Failure); +} + +template +CHIP_ERROR EncodeRead(AttributeValueEncoder & aEncoder, const F & getter) +{ + T ret; + CHIP_ERROR err = getter(ret); + if (err == CHIP_NO_ERROR) + { + err = aEncoder.Encode(ret); + } + + // TODO: Should the logic return these directly? I didn't want to mix the IM layer into there, but this is annoying. + return TranslateErrorToIMStatus(err); +} + +} // namespace + +CHIP_ERROR Interface::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + switch (aPath.mAttributeId) + { + case OpenDuration::Id: { + typedef OpenDuration::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetOpenDuration(ret); }); + } + case DefaultOpenDuration::Id: { + typedef DefaultOpenDuration::TypeInfo::Type T; + return EncodeRead(aEncoder, + [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetDefaultOpenDuration(ret); }); + } + case AutoCloseTime::Id: { + typedef AutoCloseTime::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetAutoCloseTime(ret); }); + } + case RemainingDuration::Id: { + typedef RemainingDuration::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetRemainingDuration(ret); }); + } + case CurrentState::Id: { + typedef CurrentState::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetCurrentState(ret); }); + } + case TargetState::Id: { + typedef TargetState::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetTargetState(ret); }); + } + case CurrentLevel::Id: { + typedef CurrentLevel::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetCurrentLevel(ret); }); + } + case TargetLevel::Id: { + typedef TargetLevel::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetTargetLevel(ret); }); + } + case DefaultOpenLevel::Id: { + typedef DefaultOpenLevel::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetDefaultOpenLevel(ret); }); + } + case ValveFault::Id: { + typedef ValveFault::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetValveFault(ret); }); + } + case LevelStep::Id: { + typedef LevelStep::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetLevelStep(ret); }); + } + case FeatureMap::Id: { + typedef FeatureMap::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetFeatureMap(ret); }); + } + case ClusterRevision::Id: { + typedef ClusterRevision::TypeInfo::Type T; + return EncodeRead(aEncoder, [&logic = mClusterLogic](T & ret) -> CHIP_ERROR { return logic.GetClusterRevision(ret); }); + } + default: + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } +} + +CHIP_ERROR Interface::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) +{ + switch (aPath.mAttributeId) + { + case DefaultOpenDuration::Id: { + DefaultOpenDuration::TypeInfo::Type val; + ReturnErrorOnFailure(aDecoder.Decode(val)); + return TranslateErrorToIMStatus(mClusterLogic.SetDefaultOpenDuration(val)); + } + break; + case DefaultOpenLevel::Id: { + DefaultOpenLevel::TypeInfo::Type val; + ReturnErrorOnFailure(aDecoder.Decode(val)); + return TranslateErrorToIMStatus(mClusterLogic.SetDefaultOpenLevel(val)); + } + default: + return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite); + } +} + +// CommandHandlerInterface +void Interface::InvokeCommand(HandlerContext & handlerContext) +{ + switch (handlerContext.mRequestPath.mCommandId) + { + case Open::Id: + HandleCommand( + handlerContext, [&logic = mClusterLogic](HandlerContext & ctx, const auto & commandData) { + // TODO: I used optional in the lower layers because I think we want to move to std::optional in general + // So here, I need to change over. But I can also change the Logic cluster to use Optional + CHIP_ERROR err = + logic.HandleOpenCommand(commandData.openDuration.std_optional(), commandData.targetLevel.std_optional()); + Status status = Status::Success; + if (err == CHIP_ERROR_INVALID_ARGUMENT) + { + status = Status::ConstraintError; + } + if (err != CHIP_NO_ERROR) + { + status = Status::Failure; + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + }); + return; + case Close::Id: + HandleCommand(handlerContext, + [&logic = mClusterLogic](HandlerContext & ctx, const auto & commandData) { + CHIP_ERROR err = logic.HandleCloseCommand(); + Status status = Status::Success; + if (err == CHIP_ERROR_INVALID_ARGUMENT) + { + status = Status::ConstraintError; + } + if (err != CHIP_NO_ERROR) + { + status = Status::Failure; + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + }); + return; + } +} + +CHIP_ERROR Interface::Init() +{ + AttributeAccessInterfaceRegistry::Instance().Register(this); + CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this); + return CHIP_NO_ERROR; +} + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.h b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.h new file mode 100644 index 00000000000000..14b96c212d1293 --- /dev/null +++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server-disco.h @@ -0,0 +1,63 @@ +/** + * + * Copyright (c) 2024 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. + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +// App should instantiate and init one Interface per endpoint +class Interface : public AttributeAccessInterface, public CommandHandlerInterface +{ +public: + Interface(EndpointId endpoint, ClusterLogic & clusterLogic) : + AttributeAccessInterface(Optional(endpoint), Id), CommandHandlerInterface(Optional(endpoint), Id), + mClusterLogic(clusterLogic) + {} + // AttributeAccessInterface + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; + + // CommandHandlerInterface + void InvokeCommand(HandlerContext & handlerContext) override; + + // Registers this handler. + CHIP_ERROR Init(); + + // TODO: Shutdown - is there a way to unregister? + +private: + // This is owned by the caller and passed to the interface for its use. + ClusterLogic & mClusterLogic; +}; + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 1923573765101a..0685c795bce8e9 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -154,6 +154,24 @@ source_set("thread-border-router-management-test-srcs") { ] } +source_set("valve-configuration-and-control-test-srcs") { + sources = [ + "${chip_root}/src/app/cluster-building-blocks/QuieterReporting.h", + "${chip_root}/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.cpp", + "${chip_root}/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-cluster-logic.h", + "${chip_root}/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-delegate.h", + "${chip_root}/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.cpp", + "${chip_root}/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-matter-context.h", + ] + public_deps = [ + "${chip_root}/src/app:interaction-model", + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + "${chip_root}/src/platform", + ] +} + source_set("ecosystem-information-test-srcs") { sources = [ "${chip_root}/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp", @@ -233,6 +251,7 @@ chip_test_suite("tests") { "TestTestEventTriggerDelegate.cpp", "TestTimeSyncDataProvider.cpp", "TestTimedHandler.cpp", + "TestValveConfigurationAndControl.cpp", "TestWriteInteraction.cpp", ] @@ -247,6 +266,7 @@ chip_test_suite("tests") { ":power-cluster-test-srcs", ":thread-network-directory-test-srcs", ":time-sync-data-provider-test-srcs", + ":valve-configuration-and-control-test-srcs", "${chip_root}/src/app", "${chip_root}/src/app:attribute-persistence", "${chip_root}/src/app/common:cluster-objects", diff --git a/src/app/tests/TestValveConfigurationAndControl.cpp b/src/app/tests/TestValveConfigurationAndControl.cpp new file mode 100644 index 00000000000000..e8f5180aaf3368 --- /dev/null +++ b/src/app/tests/TestValveConfigurationAndControl.cpp @@ -0,0 +1,2250 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip::System::Clock::Literals; +namespace chip { + +namespace System { + +// TODO: This might be worthwhile to generalize and put into the system layer, but it too will need unit tests. +class TimerAndMockClock : public Clock::Internal::MockClock, public Layer +{ +public: + // System Layer overrides + CHIP_ERROR Init() override { return CHIP_NO_ERROR; } + void Shutdown() override { Clear(); } + void Clear() + { + mTimerList.Clear(); + mTimerNodes.ReleaseAll(); + } + bool IsInitialized() const override { return true; } + + CHIP_ERROR StartTimer(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) override + { + Clock::Timestamp awakenTime = GetMonotonicMilliseconds64() + std::chrono::duration_cast(aDelay); + TimerList::Node * node = mTimerNodes.Create(*this, awakenTime, aComplete, aAppState); + mTimerList.Add(node); + return CHIP_NO_ERROR; + } + void CancelTimer(TimerCompleteCallback aComplete, void * aAppState) override + { + TimerList::Node * cancelled = mTimerList.Remove(aComplete, aAppState); + if (cancelled != nullptr) + { + mTimerNodes.Release(cancelled); + } + } + CHIP_ERROR ExtendTimerTo(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) override + { + return mTimerList.GetRemainingTime(onComplete, appState) != Clock::Timeout(0); + } + Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) override + { + return mTimerList.GetRemainingTime(onComplete, appState); + } + CHIP_ERROR ScheduleWork(TimerCompleteCallback aComplete, void * aAppState) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Clock overrides + void SetMonotonic(Clock::Milliseconds64 timestamp) + { + MockClock::SetMonotonic(timestamp); + // Find all the timers that fired at this time or before and invoke the callbacks + TimerList::Node * node; + while ((node = mTimerList.Earliest()) != nullptr && node->AwakenTime() <= timestamp) + { + mTimerList.PopEarliest(); + // Invoke auto-releases + mTimerNodes.Invoke(node); + } + } + + void AdvanceMonotonic(Clock::Milliseconds64 increment) { SetMonotonic(GetMonotonicMilliseconds64() + increment); } + +private: + TimerPool<> mTimerNodes; + TimerList mTimerList; +}; + +} // namespace System +namespace app { +namespace Clusters { +namespace ValveConfigurationAndControl { + +namespace { +// These are globals because SetUpTestSuite is static and I'm not shaving that yak today. +System::TimerAndMockClock gSystemLayerAndClock = System::TimerAndMockClock(); +System::Clock::ClockBase * gSavedClock = nullptr; + +constexpr uint16_t kExpectedClusterRevision = 1u; +} // namespace + +class TestValveConfigurationAndControlClusterLogic : public ::testing::Test +{ +public: + static void SetUpTestSuite() + { + ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); + gSavedClock = &System::SystemClock(); + System::Clock::Internal::SetSystemClockForTesting(&gSystemLayerAndClock); + DeviceLayer::SetSystemLayerForTesting(&gSystemLayerAndClock); + } + static void TearDownTestSuite() + { + gSystemLayerAndClock.Shutdown(); + DeviceLayer::SetSystemLayerForTesting(nullptr); + System::Clock::Internal::SetSystemClockForTesting(gSavedClock); + Platform::MemoryShutdown(); + } + +private: +}; + +class TestDelegateLevel : public LevelControlDelegate +{ +public: + TestDelegateLevel() {} + CHIP_ERROR HandleOpenValve(const Percent targetLevel, Percent & currentLevel, BitMask & valveFault) override + { + lastRequestedLevel = targetLevel; + ++numHandleOpenValveCalls; + if (simulateAsync) + { + currentLevel = targetLevel / 2; + } + else + { + currentLevel = targetLevel; + } + level = currentLevel; + target = targetLevel; + if (simulateOpenFailure || simulateValveFaultNoFailure) + { + valveFault = simulatedFailureBitMask; + } + if (simulateOpenFailure) + { + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; + } + virtual CHIP_ERROR HandleCloseValve(Percent & currentLevel, BitMask & valveFault) override + { + ++numHandleCloseValveCalls; + if (simulateAsync) + { + currentLevel = 10; + } + else + { + currentLevel = 0; + } + level = currentLevel; + if (simulateCloseFailure || simulateValveFaultNoFailure) + { + valveFault = simulatedFailureBitMask; + } + if (simulateCloseFailure) + { + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; + } + + Percent GetCurrentValveLevel() override + { + // TODO: maybe want to ramp this. + level = target; + return level; + } + + BitMask simulatedFailureBitMask = + BitMask(to_underlying(ValveFaultBitmap::kBlocked) | to_underlying(ValveFaultBitmap::kGeneralFault)); + Percent lastRequestedLevel = 0; + bool simulateOpenFailure = false; + bool simulateCloseFailure = false; + bool simulateValveFaultNoFailure = false; + bool simulateAsync = false; + int numHandleOpenValveCalls = 0; + int numHandleCloseValveCalls = 0; + Percent level = 0; + Percent target = 0; +}; + +class TestDelegateNoLevel : public NonLevelControlDelegate +{ +public: + TestDelegateNoLevel() {} + CHIP_ERROR HandleOpenValve(ValveStateEnum & currentState, BitMask & valveFault) override + { + ++numHandleOpenValveCalls; + if (simulateAsync) + { + currentState = ValveStateEnum::kTransitioning; + } + else + { + currentState = ValveStateEnum::kOpen; + } + state = currentState; + target = ValveStateEnum::kOpen; + if (simulateOpenFailure || simulateValveFaultNoFailure) + { + valveFault = simulatedFailureBitMask; + } + if (simulateOpenFailure) + { + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; + } + CHIP_ERROR HandleCloseValve(ValveStateEnum & currentState, BitMask & valveFault) override + { + ++numHandleCloseValveCalls; + if (simulateAsync) + { + currentState = ValveStateEnum::kTransitioning; + } + else + { + currentState = ValveStateEnum::kClosed; + } + state = currentState; + target = ValveStateEnum::kClosed; + if (simulateCloseFailure || simulateValveFaultNoFailure) + { + valveFault = simulatedFailureBitMask; + } + if (simulateCloseFailure) + { + return CHIP_ERROR_INTERNAL; + } + return CHIP_NO_ERROR; + } + ValveStateEnum GetCurrentValveState() override + { + // Might want to have called more than one time before hitting the target. + state = target; + return state; + } + + BitMask simulatedFailureBitMask = + BitMask(to_underlying(ValveFaultBitmap::kBlocked) | to_underlying(ValveFaultBitmap::kGeneralFault)); + bool simulateOpenFailure = false; + bool simulateCloseFailure = false; + bool simulateValveFaultNoFailure = false; + bool simulateAsync = false; + int numHandleOpenValveCalls = 0; + int numHandleCloseValveCalls = 0; + ValveStateEnum state = ValveStateEnum::kUnknownEnumValue; + ValveStateEnum target = ValveStateEnum::kUnknownEnumValue; +}; + +class MockedMatterContext : public MatterContext +{ +public: + MockedMatterContext(EndpointId endpoint, PersistentStorageDelegate & persistentStorageDelegate) : + MatterContext(endpoint, persistentStorageDelegate) + {} + void MarkDirty(AttributeId id) override { mDirtyMarkedList.push_back(id); } + std::vector GetDirtyList() { return mDirtyMarkedList; } + void ClearDirtyList() { mDirtyMarkedList.clear(); } + ~MockedMatterContext() { gSystemLayerAndClock.Clear(); } + +private: + // Won't handle double-marking an attribute, so don't do that in tests + std::vector mDirtyMarkedList; +}; + +//========================================================================================= +// Tests for conformance +//========================================================================================= + +// This test ensures that the Init function properly errors when the conformance is valid and passes otherwise. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestConformanceValid) +{ + // Nothing on, should be valid + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false + }; + EXPECT_TRUE(conformance.Valid()); + + // LVL on, no optional stuff, should be valid + conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_TRUE(conformance.Valid()); + + // LVL on, one optional level thing + conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_TRUE(conformance.Valid()); + + // LVL on, one optional level thing + conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = true }; + EXPECT_TRUE(conformance.Valid()); + + // LVL on, two optional level things + conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = false, + .supportsLevelStep = true }; + EXPECT_TRUE(conformance.Valid()); + + // Fully optional thing on + conformance = { .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false }; + EXPECT_TRUE(conformance.Valid()); + + // TS on + conformance = { .featureMap = to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_TRUE(conformance.Valid()); + + // Both features on + conformance = { .featureMap = to_underlying(Feature::kTimeSync) | to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_TRUE(conformance.Valid()); + + // LVL off, one optional level thing on + conformance = { .featureMap = 0, .supportsDefaultOpenLevel = true, .supportsValveFault = false, .supportsLevelStep = false }; + EXPECT_FALSE(conformance.Valid()); + + // LVL on, other optional level thing on + conformance = { .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = true }; + EXPECT_FALSE(conformance.Valid()); + + // Bad feature map + conformance = { .featureMap = 0x04, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = true }; + EXPECT_FALSE(conformance.Valid()); +} + +// The cluster requires different versions of the delegate depending on the supported feature set. +// This test ensures the Init function corectly errors if the supplied delegate does not match +// the given cluster conformance. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestWrongDelegates) +{ + TestDelegateLevel delegateLevel; + TestDelegateNoLevel delegateNoLevel; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logicLevel(delegateLevel, context); + ClusterLogic logicNoLevel(delegateNoLevel, context); + ClusterConformance conformanceLevel = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + ClusterConformance conformanceNoLevel = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false + }; + + EXPECT_EQ(logicLevel.Init(conformanceNoLevel), CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR); + EXPECT_EQ(logicNoLevel.Init(conformanceLevel), CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR); +} + +//========================================================================================= +// Tests for getters +//========================================================================================= + +// This test ensures that all getters are properly implemented and return valid values after successful initialization. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestGetAttributesAllFeatures) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + MockedMatterContext context(0, storageDelegate); + ClusterLogic logic(delegate, context); + + // Everything on, all should return values + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEpochUsNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + Percent valPercent; + uint8_t val8; + BitMask valBitmap; + Attributes::FeatureMap::TypeInfo::Type featureMap; + Attributes::ClusterRevision::TypeInfo::Type clusterRevision; + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetAutoCloseTime(valEpochUsNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEpochUsNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetDefaultOpenLevel(valPercent), CHIP_NO_ERROR); + EXPECT_EQ(valPercent, 100); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap.Raw(), 0); + + EXPECT_EQ(logic.GetLevelStep(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, 1); + + EXPECT_EQ(logic.GetFeatureMap(featureMap), CHIP_NO_ERROR); + EXPECT_EQ(featureMap, conformance.featureMap); + + EXPECT_EQ(logic.GetClusterRevision(clusterRevision), CHIP_NO_ERROR); + EXPECT_EQ(clusterRevision, kExpectedClusterRevision); +} + +// This test ensures that attributes that are not supported by the conformance properly return errors +// and attributes that are supported return values properly. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestGetAttributesNoFeatures) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + MockedMatterContext context(0, storageDelegate); + ClusterLogic logic(delegate, context); + + // Everything on, all should return values + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEpochUsNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + Percent valPercent; + uint8_t val8; + BitMask valBitmap; + Attributes::FeatureMap::TypeInfo::Type featureMap; + Attributes::ClusterRevision::TypeInfo::Type clusterRevision; + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetAutoCloseTime(valEpochUsNullable), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetDefaultOpenLevel(valPercent), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetLevelStep(val8), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + EXPECT_EQ(logic.GetFeatureMap(featureMap), CHIP_NO_ERROR); + EXPECT_EQ(featureMap, conformance.featureMap); + + EXPECT_EQ(logic.GetClusterRevision(clusterRevision), CHIP_NO_ERROR); + EXPECT_EQ(clusterRevision, kExpectedClusterRevision); +} + +// This test ensures that all attribute getters return the given starting state values before changes. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestGetAttributesStartingState) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + MockedMatterContext context(0, storageDelegate); + ClusterLogic logic(delegate, context); + + // Everything on, all should return values + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + ClusterInitParameters initialState = { + .currentState = DataModel::MakeNullable(ValveStateEnum::kTransitioning), + .currentLevel = DataModel::MakeNullable(static_cast(50u)), + .valveFault = BitMask(ValveFaultBitmap::kLeaking), + .levelStep = 2u, + }; + EXPECT_EQ(logic.Init(conformance, initialState), CHIP_NO_ERROR); + + ClusterState state; + state.currentState = initialState.currentState; + state.currentLevel = initialState.currentLevel; + state.valveFault = initialState.valveFault; + state.levelStep = initialState.levelStep; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEpochUsNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + Percent valPercent; + uint8_t val8; + BitMask valBitmap; + Attributes::FeatureMap::TypeInfo::Type featureMap; + Attributes::ClusterRevision::TypeInfo::Type clusterRevision; + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, state.openDuration); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, state.defaultOpenDuration); + + EXPECT_EQ(logic.GetAutoCloseTime(valEpochUsNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEpochUsNullable, state.autoCloseTime); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, state.remainingDuration.value()); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, state.currentState); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, state.targetState); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, state.currentLevel); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, state.targetLevel); + + EXPECT_EQ(logic.GetDefaultOpenLevel(valPercent), CHIP_NO_ERROR); + EXPECT_EQ(valPercent, state.defaultOpenLevel); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, state.valveFault); + + EXPECT_EQ(logic.GetLevelStep(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, state.levelStep); + + EXPECT_EQ(logic.GetFeatureMap(featureMap), CHIP_NO_ERROR); + EXPECT_EQ(featureMap, conformance.featureMap); + + EXPECT_EQ(logic.GetClusterRevision(clusterRevision), CHIP_NO_ERROR); + EXPECT_EQ(clusterRevision, kExpectedClusterRevision); +} + +// This test ensures that all attribute getter functions properly error on an uninitialized cluster. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestGetAttributesUninitialized) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + MockedMatterContext context(0, storageDelegate); + ClusterLogic logic(delegate, context); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEpochUsNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + Percent valPercent; + uint8_t val8; + BitMask valBitmap; + Attributes::FeatureMap::TypeInfo::Type featureMap; + Attributes::ClusterRevision::TypeInfo::Type clusterRevision; + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetAutoCloseTime(valEpochUsNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetDefaultOpenLevel(valPercent), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetLevelStep(val8), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetFeatureMap(featureMap), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(logic.GetClusterRevision(clusterRevision), CHIP_ERROR_INCORRECT_STATE); +} + +//========================================================================================= +// Tests for setters +//========================================================================================= + +// This test ensures that the Set function for DefaultOpenDuration sets the value properly including +// - constraints checks +// - value pushed through to persistent storage correctly +// - value is persisted on a per-endpoint basis +// - cluster logic loads saved values at startup +// - cluster logic operates only on the stored values for the correct endpoint +// - Set function correctly errors when the persistent storage errors +TEST_F(TestValveConfigurationAndControlClusterLogic, TestSetDefaultOpenDuration) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + DataModel::Nullable valElapsedSNullable; + + // Setting this value before initialization should fail + auto testVal = DataModel::MakeNullable(static_cast(5u)); + EXPECT_EQ(logic.SetDefaultOpenDuration(testVal), CHIP_ERROR_INCORRECT_STATE); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // Lowest possible value + testVal = DataModel::MakeNullable(static_cast(1u)); + EXPECT_EQ(logic.SetDefaultOpenDuration(testVal), CHIP_NO_ERROR); + + // Ensure the value is accessible via the API + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, testVal); + + // Ensure the value is persisted in the test storage + StorageKeyName keyName = DefaultStorageKeyAllocator::VCCDefaultOpenDuration(endpoint); + uint32_t persistedValue; + uint16_t size = static_cast(sizeof(persistedValue)); + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal.Value()); + // Test that this doesn't exist for other endpoints. Check 1. + EXPECT_FALSE(storageDelegate.HasKey(DefaultStorageKeyAllocator::VCCDefaultOpenDuration(1).KeyName())); + + testVal = DataModel::MakeNullable(static_cast(12u)); + EXPECT_EQ(logic.SetDefaultOpenDuration(testVal), CHIP_NO_ERROR); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, testVal); + + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal.Value()); + + auto outOfRangeVal = DataModel::MakeNullable(static_cast(0u)); + EXPECT_EQ(logic.SetDefaultOpenDuration(outOfRangeVal), CHIP_ERROR_INVALID_ARGUMENT); + + // Ensure the value wasn't changed + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, testVal); + + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal.Value()); + + // Test that firing up a new logic cluster on the same endpoint loads the value from persisted storage + ClusterLogic logic_same_endpoint = ClusterLogic(delegate, context); + EXPECT_EQ(logic_same_endpoint.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic_same_endpoint.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, testVal); + + // Test that a new logic cluster on a different endpoint does not load the value + MockedMatterContext context_ep1 = MockedMatterContext(1, storageDelegate); + ClusterLogic logic_different_endpoint = ClusterLogic(delegate, context_ep1); + EXPECT_EQ(logic_different_endpoint.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic_different_endpoint.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // Test setting back to null, this should clear the persisted value + testVal = DataModel::NullNullable; + EXPECT_EQ(logic.SetDefaultOpenDuration(testVal), CHIP_NO_ERROR); + EXPECT_FALSE(storageDelegate.HasKey(keyName.KeyName())); + + // Check that the value is not loaded when a new logic cluster is created + ClusterLogic logic_same_endpoint_again = ClusterLogic(delegate, context); + EXPECT_EQ(logic_same_endpoint_again.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic_same_endpoint_again.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // Test that the calling function fails when write fails are on + storageDelegate.SetRejectWrites(true); + testVal.SetNonNull(12u); + EXPECT_EQ(logic.SetDefaultOpenDuration(testVal), CHIP_ERROR_PERSISTED_STORAGE_FAILED); +} + +// This test ensures that the Set function for DefaultOpenDuration sets the value properly including +// - constraints checks +// - value pushed through to persistent storage correctly +// - value is persisted on a per-endpoint basis +// - cluster logic loads saved values at startup +// - cluster logic operates only on the stored values for the correct endpoint +// - Set function correctly errors when the persistent storage errors +// - Set function errors when the OpenLevel attribute is not supported +// Conformance to the LevelStep value is tested in another test +TEST_F(TestValveConfigurationAndControlClusterLogic, TestSetDefaultOpenLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + uint8_t val8; + + // Setting this value before initialization should fail + uint8_t testVal = 5u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_ERROR_INCORRECT_STATE); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, 100u); + + // Lowest possible value + testVal = 1u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + + // Ensure the value is accessible via the API + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + // Ensure the value is persisted in the test storage + StorageKeyName keyName = DefaultStorageKeyAllocator::VCCDefaultOpenLevel(endpoint); + uint8_t persistedValue; + uint16_t size = static_cast(sizeof(persistedValue)); + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal); + // Test that this doesn't exist for other endpoints. Check 1. + EXPECT_FALSE(storageDelegate.HasKey(DefaultStorageKeyAllocator::VCCDefaultOpenLevel(1).KeyName())); + + // Highest possible value + testVal = 100u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal); + + uint8_t outOfRangeVal = 0u; + EXPECT_EQ(logic.SetDefaultOpenLevel(outOfRangeVal), CHIP_ERROR_INVALID_ARGUMENT); + outOfRangeVal = 101u; + EXPECT_EQ(logic.SetDefaultOpenLevel(outOfRangeVal), CHIP_ERROR_INVALID_ARGUMENT); + + // Ensure the value wasn't changed + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + EXPECT_TRUE(storageDelegate.HasKey(keyName.KeyName())); + EXPECT_EQ(storageDelegate.SyncGetKeyValue(keyName.KeyName(), &persistedValue, size), CHIP_NO_ERROR); + EXPECT_EQ(persistedValue, testVal); + + // Set Non-default value + testVal = 12u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + // Test that firing up a new logic cluster on the same endpoint loads the value from persisted storage + ClusterLogic logic_same_endpoint = ClusterLogic(delegate, context); + EXPECT_EQ(logic_same_endpoint.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic_same_endpoint.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + // Test that a new logic cluster on a different endpoint does not load the value + MockedMatterContext context_ep1 = MockedMatterContext(1, storageDelegate); + ClusterLogic logic_different_endpoint = ClusterLogic(delegate, context_ep1); + EXPECT_EQ(logic_different_endpoint.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic_different_endpoint.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, 100u); + + // Test that the calling function fails when write fails are on + storageDelegate.SetRejectWrites(true); + testVal = 15u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_ERROR_PERSISTED_STORAGE_FAILED); + storageDelegate.SetRejectWrites(false); + + // Test that we get an error if this attribute is not supported + ClusterLogic logic_no_level = ClusterLogic(delegate, context); + ClusterConformance conformance_no_level = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = false, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic_no_level.Init(conformance_no_level), CHIP_NO_ERROR); + EXPECT_EQ(logic_no_level.SetDefaultOpenLevel(testVal), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); +} + +// This test ensures that the SetDefaultOpenLevel function correctly assesses the set value with respect to the LevelStep. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestSetDefaultOpenLevelWithLevelStep) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + uint8_t val8; + uint8_t testVal; + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + ClusterInitParameters state; + state.levelStep = 45; + EXPECT_EQ(logic.Init(conformance, state), CHIP_NO_ERROR); + + // Default is 100 + // 45, 90 and 100 should work, others should fail. + testVal = 45u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + testVal = 90u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + testVal = 100u; + EXPECT_EQ(logic.SetDefaultOpenLevel(testVal), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); + + // Test a value that's not in the level step and ensure the value is not changed. + EXPECT_EQ(logic.SetDefaultOpenLevel(33u), CHIP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(logic.GetDefaultOpenLevel(val8), CHIP_NO_ERROR); + EXPECT_EQ(val8, testVal); +} + +//========================================================================================= +// Tests for Open command parameters +//========================================================================================= + +// This test ensures that the OpenDuration is set correctly when Open is called. Tests: +// - Fall back to Null when omitted and DefaultOpenDuration is null +// - Fall back to DefaultOpenDuration when omitted and DefaultOpenDuration is set +// - Null and value are both accepted when the parameter is supplied in the command field. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenDuration) +{ + + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(DataModel::NullNullable), std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // Fall back to default + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + DataModel::Nullable defaultOpenDuration; + defaultOpenDuration.SetNonNull(12u); + EXPECT_EQ(logic.SetDefaultOpenDuration(defaultOpenDuration), CHIP_NO_ERROR); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, defaultOpenDuration); + + // Set from command parameters + DataModel::Nullable openDuration; + openDuration.SetNull(); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + openDuration.SetNonNull(12u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable.ValueOr(0), 12u); +} + +// This test ensures that the Open command correctly errors when the TargetLevel field is +// set for devices that do not support the LVL feature. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelFeatureUnsupported) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + + // Should get an error when this is called with target level set since the feature is unsupported. + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(50u)), CHIP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); +} + +// This test ensures that The Open command correctly falls back to the spec-defined default when the target level +// is omitted and the DefaultOpenLevel is not supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelNotSuppliedNoDefaultSupported) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 100u); +} + +// This test ensures that the Open command correclty calls back to the DefaultOpenLevel when the target level +// is omitted and the DefaultOpenLevel is supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelNotSuppliedDefaultSupported) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = false, + .supportsLevelStep = false }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 100u); + + EXPECT_EQ(logic.SetDefaultOpenLevel(50u), CHIP_NO_ERROR); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 50u); +} + +// This test ensures the Open command uses the supplied target level if it is supplied. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelSupplied) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = false, + .supportsLevelStep = true }; + ClusterInitParameters state; + state.levelStep = 33; + EXPECT_EQ(logic.Init(conformance, state), CHIP_NO_ERROR); + + // 33, 66, 99 and 100 should all work, nothing else should + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(33u)), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 33u); + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(66u)), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 66u); + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(99u)), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 99u); + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(100u)), CHIP_NO_ERROR); + EXPECT_EQ(delegate.lastRequestedLevel, 100u); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 4); + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(32u)), CHIP_ERROR_INVALID_ARGUMENT); + // Ensure this wasn't called again. + EXPECT_EQ(delegate.lastRequestedLevel, 100u); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 4); +} + +//========================================================================================= +// Tests for Open Command handlers - current and target values +//========================================================================================= + +// This test ensures the target and current values are set correcly when the delegate is +// able to open the value immediately. Cluster supports LVL feature +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenImmediateLevelSupported) +{ + // Testing that current level, target level, target state and current state are set correctly + // If the delegate is able to open the valve fully during the handler call + // then the current level and current state should indicate open and the appropriate level. + // TargetLevel and TargetState should be NULL. + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = false; + delegate.simulateAsync = false; + + DataModel::Nullable level; + DataModel::Nullable state; + uint8_t targetLevel; + + targetLevel = 100u; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(targetLevel)), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel); + EXPECT_EQ(logic.GetTargetLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level, DataModel::NullNullable); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state, DataModel::NullNullable); + + targetLevel = 50u; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(targetLevel)), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel); + EXPECT_EQ(logic.GetTargetLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level, DataModel::NullNullable); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state, DataModel::NullNullable); + + EXPECT_EQ(delegate.numHandleOpenValveCalls, 2); +} + +// This test ensures the target and current values are set correcly when the delegate is +// able to open the value immediately. Cluster does not support LVL feature +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenImmediateLevelNotSupported) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = false; + delegate.simulateAsync = false; + + DataModel::Nullable state; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state, DataModel::NullNullable); + + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); +} + +// This test ensures the target and current values are set correcly when the delegate is +// not able to open the value immediately. Cluster supports LVL feature +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenAsyncLevelSupported) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = false; + delegate.simulateAsync = true; + + DataModel::Nullable level; + DataModel::Nullable state; + uint8_t targetLevel; + + targetLevel = 100u; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(targetLevel)), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel / 2); + EXPECT_EQ(logic.GetTargetLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kTransitioning); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + + targetLevel = 50u; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(targetLevel)), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel / 2); + EXPECT_EQ(logic.GetTargetLevel(level), CHIP_NO_ERROR); + EXPECT_EQ(level.ValueOr(0), targetLevel); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kTransitioning); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + + EXPECT_EQ(delegate.numHandleOpenValveCalls, 2); +} + +// This test ensures the target and current values are set correcly when the delegate is +// not able to open the value immediately. Cluster does not support LVL feature +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenAsyncLevelNotSupported) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false, + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = false; + delegate.simulateAsync = true; + + DataModel::Nullable state; + + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetCurrentState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kTransitioning); + EXPECT_EQ(logic.GetTargetState(state), CHIP_NO_ERROR); + EXPECT_EQ(state.ValueOr(ValveStateEnum::kUnknownEnumValue), ValveStateEnum::kOpen); + + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); +} + +//========================================================================================= +// Tests for Open Command handlers - valve faults +//========================================================================================= + +// Test ensures valve faults are handled correctly when the valve can still be opened. LVL feature supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedNoErrorLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + BitMask valveFault; + + delegate.simulateValveFaultNoFailure = true; + delegate.simulatedFailureBitMask = BitMask(to_underlying(ValveFaultBitmap::kLeaking)); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + EXPECT_EQ(logic.GetValveFault(valveFault), CHIP_NO_ERROR); + EXPECT_EQ(valveFault, delegate.simulatedFailureBitMask); +} + +// Test ensures valve faults are handled correctly when the valve can still be opened. LVL feature not supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedNoErrorNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, + .supportsDefaultOpenLevel = false, + .supportsValveFault = true, + .supportsLevelStep = false, + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + BitMask valveFault; + + delegate.simulateValveFaultNoFailure = true; + delegate.simulatedFailureBitMask = BitMask(to_underlying(ValveFaultBitmap::kLeaking)); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + EXPECT_EQ(logic.GetValveFault(valveFault), CHIP_NO_ERROR); + EXPECT_EQ(valveFault, delegate.simulatedFailureBitMask); +} + +// Test ensures valve faults are handled correctly when the valve cannot be opened. LVL feature supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedErrorLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + BitMask valveFault; + + delegate.simulateOpenFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + EXPECT_EQ(logic.GetValveFault(valveFault), CHIP_NO_ERROR); + EXPECT_EQ(valveFault, delegate.simulatedFailureBitMask); +} + +// Test ensures valve faults are handled correctly when the valve cannot be opened. LVL feature not supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedErrorNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, + .supportsDefaultOpenLevel = false, + .supportsValveFault = true, + .supportsLevelStep = false, + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + BitMask valveFault; + + delegate.simulateOpenFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + EXPECT_EQ(logic.GetValveFault(valveFault), CHIP_NO_ERROR); + EXPECT_EQ(valveFault, delegate.simulatedFailureBitMask); +} + +// Test ensures returned valve faults do not cause cluster problems if returned from the delegate when the valve feature is not +// supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedErrorLevelFaultNotSupported) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel), + .supportsDefaultOpenLevel = true, + .supportsValveFault = false, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + + delegate.simulateOpenFailure = false; + delegate.simulateValveFaultNoFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 2); +} + +// Test ensures returned valve faults do not cause cluster problems if returned from the delegate when the valve feature is not +// supported. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenFaultReturnedErrorNoLevelFaultNotSupported) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, + .supportsDefaultOpenLevel = false, + .supportsValveFault = false, + .supportsLevelStep = false, + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateOpenFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 1); + + delegate.simulateOpenFailure = false; + delegate.simulateValveFaultNoFailure = true; + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleOpenValveCalls, 2); +} + +bool HasAttributeChanges(std::vector changes, AttributeId id) +{ + return std::find(changes.begin(), changes.end(), id) != changes.end(); +} +//========================================================================================= +// Tests for timing for Attribute updates and RemainingDuration Q +//========================================================================================= +// Tests that remaining duration is updated when openLevel is set to a value or to to null +// Tests that attribute reports are sent out for attributes changed on Open call +// Tests that attribute reports are sent for RemainingDuration at Q times. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestAttributeUpdates) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valPercentNullable; + + // When null, RemainingDuration should also be null. No updates are expected. + context.ClearDirtyList(); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(DataModel::NullNullable), std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // We should see the CurrentLevel and CurrentState marked as dirty because they are changed. + // TargetState and TargetLevel should ALSO be marked as dirty because the target state should transition through + // from target back to NULL per the spec (effect of receipt for open command). + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + // When non-null RemainingDuration should initially be set to OpenDuration and this should trigger + // an attribute change callback. Attribute change callbacks should happen no more than once per second + // and at the end. + // This test tests attribute callbacks on an open duration that runs to the end. The OpenDuration is + // in units of seconds, so the close call will come at units of seconds. + gSystemLayerAndClock.SetMonotonic(0_ms64); + context.ClearDirtyList(); + + DataModel::Nullable openDuration; + openDuration.SetNonNull(12u); + Percent requestedLevel = 50u; + // Test also that we get an attribute update + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::make_optional(requestedLevel)), CHIP_NO_ERROR); + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable.ValueOr(0), openDuration.Value()); + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable.ValueOr(0), requestedLevel); + // We should see the following attributes marked as dirty since they are different at the end + // of the command: currentLevel, remainingDuration, openDuration + // The TargetLevel and TargetState should be NULL at the end of the open call, but will still be + // marked dirty per the spec since they are set to the target values and and back to NULL. + // Similarly, TargetState is also set to transitioning during the call then back to open and should + // therefore be marked as dirty. + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetState::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(500_ms64); + // No new reports should be happening in this time. + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + // Even if we read the remaining duration to force an update, we shouldn't expect an attribute change report + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + // At 1s, the system should generate a report. + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(500_ms64); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable.ValueOr(0), openDuration.Value() - 1); + + // Nothing else should have changed + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + // At 1.7s, the system should not generate a report even if we read the value to force and update. + // The new duration should be reflected (rounded to nearest s) + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(700_ms64); + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable.ValueOr(0), openDuration.Value() - 2); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + // At 2s, we should get an attribute change report even if we don't read. + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(300_ms64); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + // Only that one attribute should be reporting a change. + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetLevel::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::TargetState::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + // Clear out all the attribute reports at 11.5 seconds. This should trigger the prior timer. + gSystemLayerAndClock.SetMonotonic(11500_ms64); + context.ClearDirtyList(); + + // Ensure we get a report at 12s and that the value goes back to Null + gSystemLayerAndClock.AdvanceMonotonic(500_ms64); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + + // Everything else should also be reporting changes as they flip back to their defaults. + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + context.ClearDirtyList(); + // Ensure we don't get a further report generated by reading + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + // Test increasing the open duration on a non-second order to see that we get an attribute change callback for the change + // up. + gSystemLayerAndClock.SetMonotonic(0_ms64); + context.ClearDirtyList(); + + openDuration.SetNonNull(12u); + requestedLevel = 50u; + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::make_optional(requestedLevel)), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(500_ms64); + // At 0.5s, we wouldn't normally expect a report for duration, but if we send a new open to increase the time, we should. + openDuration.SetNonNull(15u); + requestedLevel = 50u; + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::make_optional(requestedLevel)), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + // Current level should remain unchanged and therefore won't be marked as dirty, but the current state is unconditionally + // set to transitioning and then back to open per the spec and therefore should be marked as dirty. + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + + // TODO: Clarify, should we get a report if we use open to set the remaining duration down? I think so. + // TODO: Add such tests here. I don't think the underlying layer handles that properly right now. + + // Just before the next report should go out, we set this to NULL. We should get a report immediately. + gSystemLayerAndClock.AdvanceMonotonic(400_ms64); + + openDuration.SetNull(); + requestedLevel = 100u; + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::make_optional(requestedLevel)), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentLevel::Id)); + // Current state will also be marked dirty since every open call sets the current state to transitioning + // unconditionally even if it is then set back to the original value. + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::CurrentState::Id)); + + context.ClearDirtyList(); + + // Check that we DON'T get a report from the previous Open call, which had a remaining duration timer on it. + gSystemLayerAndClock.AdvanceMonotonic(100_ms64); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); +} + +// Tests that RemainingDuration gets updated correctly even if the timer doesn't fire on the exact ms (it doesn't). +TEST_F(TestValveConfigurationAndControlClusterLogic, TestAttributeUpdatesNonExactClock) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(550_ms64); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + context.ClearDirtyList(); + gSystemLayerAndClock.AdvanceMonotonic(550_ms64); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_FALSE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + + context.ClearDirtyList(); + gSystemLayerAndClock.SetMonotonic(2100_ms64); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::RemainingDuration::Id)); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); +} + +//========================================================================================= +// Tests for automatically calling close from the cluster logic +//========================================================================================= +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + // No valve faults, level should say 0, state should say closed, targets should be null + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kClosed)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::Nullable(0)); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(0)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + // No valve faults, level should say 0, state should say closed, targets should be null + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kClosed)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(0)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationLevelAsync) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateAsync = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + // No valve faults, level should say 0, state should say closed, targets should be null + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kTransitioning)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_NE(valPercentNullable, DataModel::Nullable(0)); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(0)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationNoLevelAsync) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateAsync = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kTransitioning)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(0)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationLevelOkFaultReturned) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulatedFailureBitMask = BitMask(to_underlying(ValveFaultBitmap::kLeaking)); + delegate.simulateValveFaultNoFailure = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kClosed)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::Nullable(0)); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(delegate.simulatedFailureBitMask)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationNoLevelOkFaultReturned) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulatedFailureBitMask = BitMask(to_underlying(ValveFaultBitmap::kLeaking)); + delegate.simulateValveFaultNoFailure = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::Nullable(ValveStateEnum::kClosed)); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(delegate.simulatedFailureBitMask)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationLevelFailure) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateCloseFailure = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + DataModel::Nullable valPercentNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetLevel(valPercentNullable), CHIP_NO_ERROR); + EXPECT_EQ(valPercentNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(delegate.simulatedFailureBitMask)); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCalledAtOpenDurationNoLevelFailure) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateCloseFailure = true; + + DataModel::Nullable valElapsedSNullable; + DataModel::Nullable valEnumNullable; + BitMask valBitmap; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(2000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetRemainingDuration(valElapsedSNullable), CHIP_NO_ERROR); + EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetCurrentState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetTargetState(valEnumNullable), CHIP_NO_ERROR); + EXPECT_EQ(valEnumNullable, DataModel::NullNullable); + + EXPECT_EQ(logic.GetValveFault(valBitmap), CHIP_NO_ERROR); + EXPECT_EQ(valBitmap, BitMask(delegate.simulatedFailureBitMask)); +} + +//========================================================================================= +// Tests for handling close commands +//========================================================================================= +// while remaining duration is not null and valve is open + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandOpenValveDurationLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + // Ensure the timer was cancelled + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandOpenValveDurationNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + DataModel::Nullable openDuration; + openDuration.SetNonNull(2u); + EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR); + EXPECT_TRUE(HasAttributeChanges(context.GetDirtyList(), Attributes::OpenDuration::Id)); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); + + // Ensure the timer was cancelled + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +// while remaining duration is null and valve is open +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandOpenValveNoDurationLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandOpenValveNoDurationNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +// while valve is closed +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandClosedLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandClosedNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +// Errors when Init hasn't been called. +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandClosedBeforeInit) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_ERROR_INCORRECT_STATE); +} + +// Error returns when delegate retuns error on close command +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandReturnsErrorLevel) +{ + TestDelegateLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync), + .supportsDefaultOpenLevel = true, + .supportsValveFault = true, + .supportsLevelStep = true }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateCloseFailure = true; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +TEST_F(TestValveConfigurationAndControlClusterLogic, TestCloseCommandReturnsErrorNoLevel) +{ + TestDelegateNoLevel delegate; + TestPersistentStorageDelegate storageDelegate; + EndpointId endpoint = 0; + MockedMatterContext context(endpoint, storageDelegate); + ClusterLogic logic(delegate, context); + + ClusterConformance conformance = { + .featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = true, .supportsLevelStep = false + }; + EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR); + + delegate.simulateCloseFailure = true; + + gSystemLayerAndClock.SetMonotonic(0_ms64); + gSystemLayerAndClock.Clear(); + EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 0); + + gSystemLayerAndClock.AdvanceMonotonic(1000_ms64); + EXPECT_EQ(logic.HandleCloseCommand(), CHIP_ERROR_INTERNAL); + EXPECT_EQ(delegate.numHandleCloseValveCalls, 1); +} + +//========================================================================================= +// Tests for timing for async read updates to current / target level and state +//========================================================================================= +// TODO: Should the cluster logic be responsible for reaching out to the delegate at the Q update period to get updates? +// TODO: What about for target state? Should this be drive by the cluster logic or the delegate? Maybe both? +// Pros for cluster logic: +// - delegate has fewer responsibilities, doesn't have to do things like timers for updating level +// Pros for delegate: +// - hardware might have built-in mechanisms to alert for level changes and state changes + +// Tests that GetCurrentValveLevel is called until the level hits the target +// Tests that the attribute update is not more than +// TODO: should reads in the middle of the update period go out and get the current value? Probably, but it's annoying. + +// Timing tests for async read +// Tests to ensure that we get calls to update the level and state until the target state / level is reached. +// Tests to ensure that we don't get updates on more than an X basis or at the end and the beginning + +//========================================================================================= +// Tests for attribute callbacks from delegates +//========================================================================================= +// TODO: Should the delegate call the cluster logic class direclty, or should this be piped through the delegate class so the +// app layer ONLY has to interact with the delegate? Test setter for valve fault Test attribute change notifications are sent +// out and not sent out when the attribute is change do the same value - add in prior + +//========================================================================================= +// Tests for attribute callbacks from delegates +//========================================================================================= +// Tests for events +// Test events are generated + +//========================================================================================= +// Tests for attribute callbacks from delegates +//========================================================================================= +// Tests for auto-close +// Test that the auto close time is set appropriately for open duration - add in prior test? +// should work both when the time is available at the time of the call and when the time is later set + +// TODO: Should the cluster logic query the current level and curent state from the driver at startup? + +} // namespace ValveConfigurationAndControl +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index e0cd1d2fa1d773..2e6fe4b6d5afa2 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -36,7 +36,9 @@ typedef uint16_t CommandRef; typedef uint64_t CompressedFabricId; typedef uint32_t DataVersion; typedef uint32_t DeviceTypeId; +typedef uint32_t ElapsedS; typedef uint16_t EndpointId; +typedef uint64_t EpochUs; typedef uint32_t EventId; typedef uint64_t EventNumber; typedef uint64_t FabricId; diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index b0de78d085e48d..456206f3d189ab 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -260,6 +260,13 @@ class DefaultStorageKeyAllocator // Terms and Conditions Acceptance Key // Stores the terms and conditions acceptance including terms and conditions revision, TLV encoded static StorageKeyName TermsAndConditionsAcceptance() { return StorageKeyName::FromConst("g/tc"); } + + // Valve configuration and control + static StorageKeyName VCCDefaultOpenLevel(EndpointId endpoint) { return StorageKeyName::Formatted("g/vcc/dol/%x", endpoint); } + static StorageKeyName VCCDefaultOpenDuration(EndpointId endpoint) + { + return StorageKeyName::Formatted("g/vcc/dod/%x", endpoint); + } }; } // namespace chip diff --git a/src/python_testing/TC_VALCC_2_1.py b/src/python_testing/TC_VALCC_2_1.py index c01d801b2274ae..a5199f052ee324 100644 --- a/src/python_testing/TC_VALCC_2_1.py +++ b/src/python_testing/TC_VALCC_2_1.py @@ -36,7 +36,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -72,7 +72,7 @@ def pics_TC_VALCC_2_1(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_cluster(Clusters.ValveConfigurationAndControl)) async def test_TC_VALCC_2_1(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_3_1.py b/src/python_testing/TC_VALCC_3_1.py index 14da923f40ad58..c08206acfbae8d 100644 --- a/src/python_testing/TC_VALCC_3_1.py +++ b/src/python_testing/TC_VALCC_3_1.py @@ -34,8 +34,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue -from chip.testing.matter_testing import (AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, - async_test_body, default_matter_test_main) +from chip.testing.matter_testing import AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -69,7 +68,7 @@ def pics_TC_VALCC_3_1(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_cluster(Clusters.ValveConfigurationAndControl)) async def test_TC_VALCC_3_1(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_3_2.py b/src/python_testing/TC_VALCC_3_2.py index 9beeefe339a770..d5bfb803a408cb 100644 --- a/src/python_testing/TC_VALCC_3_2.py +++ b/src/python_testing/TC_VALCC_3_2.py @@ -37,7 +37,7 @@ from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status from chip.testing.matter_testing import (AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, - async_test_body, default_matter_test_main) + default_matter_test_main, has_feature, run_if_endpoint_matches) from mobly import asserts @@ -74,7 +74,7 @@ def pics_TC_VALCC_3_2(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_feature(Clusters.ValveConfigurationAndControl, Clusters.ValveConfigurationAndControl.Bitmaps.Feature.kLevel)) async def test_TC_VALCC_3_2(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_3_3.py b/src/python_testing/TC_VALCC_3_3.py index 0b5cffa8896eb0..c1b0b9d17bb300 100644 --- a/src/python_testing/TC_VALCC_3_3.py +++ b/src/python_testing/TC_VALCC_3_3.py @@ -35,7 +35,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.testing.matter_testing import (AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, - async_test_body, default_matter_test_main) + default_matter_test_main, has_attribute, run_if_endpoint_matches) from mobly import asserts @@ -75,7 +75,7 @@ def pics_TC_VALCC_3_3(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_attribute(Clusters.ValveConfigurationAndControl.Attributes.DefaultOpenLevel)) async def test_TC_VALCC_3_3(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_3_4.py b/src/python_testing/TC_VALCC_3_4.py index 05782fabed4115..e59a160ca5fc9e 100644 --- a/src/python_testing/TC_VALCC_3_4.py +++ b/src/python_testing/TC_VALCC_3_4.py @@ -36,7 +36,7 @@ import chip.clusters as Clusters from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_attribute, run_if_endpoint_matches from mobly import asserts @@ -65,7 +65,7 @@ def pics_TC_VALCC_3_4(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_attribute(Clusters.ValveConfigurationAndControl.Attributes.LevelStep)) async def test_TC_VALCC_3_4(self): endpoint = self.get_endpoint(default=1) @@ -77,18 +77,7 @@ async def test_TC_VALCC_3_4(self): attribute_list = await self.read_valcc_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) self.step(3) - if attributes.LevelStep.attribute_id not in attribute_list: - logging.info("LevelStep not supported skipping test case") - - # Skipping all remainig steps - for step in self.get_test_steps(self.current_test_info.name)[self.current_step_index:]: - self.step(step.test_plan_number) - logging.info("Test step skipped") - - return - - else: - logging.info("Test step skipped") + # Steps 2 and three are handled by the decorator self.step(4) levelStep = await self.read_valcc_attribute_expect_success(endpoint=endpoint, attribute=attributes.LevelStep) diff --git a/src/python_testing/TC_VALCC_4_1.py b/src/python_testing/TC_VALCC_4_1.py index f30df68d79950a..a1179afe06d61c 100644 --- a/src/python_testing/TC_VALCC_4_1.py +++ b/src/python_testing/TC_VALCC_4_1.py @@ -37,7 +37,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -69,7 +69,7 @@ def pics_TC_VALCC_4_1(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_cluster(Clusters.ValveConfigurationAndControl)) async def test_TC_VALCC_4_1(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_4_2.py b/src/python_testing/TC_VALCC_4_2.py index 3394df61bad885..35d75ef2d95da0 100644 --- a/src/python_testing/TC_VALCC_4_2.py +++ b/src/python_testing/TC_VALCC_4_2.py @@ -37,7 +37,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -72,7 +72,7 @@ def pics_TC_VALCC_4_2(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_cluster(Clusters.ValveConfigurationAndControl)) async def test_TC_VALCC_4_2(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/TC_VALCC_4_3.py b/src/python_testing/TC_VALCC_4_3.py index c43f9303d2da7b..ee19f48c2f3976 100644 --- a/src/python_testing/TC_VALCC_4_3.py +++ b/src/python_testing/TC_VALCC_4_3.py @@ -37,7 +37,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_feature, run_if_endpoint_matches from mobly import asserts @@ -69,7 +69,7 @@ def pics_TC_VALCC_4_3(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_feature(Clusters.ValveConfigurationAndControl, Clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync)) async def test_TC_VALCC_4_3(self): endpoint = self.get_endpoint(default=1) @@ -78,23 +78,8 @@ async def test_TC_VALCC_4_3(self): attributes = Clusters.ValveConfigurationAndControl.Attributes self.step("2a") - feature_map = await self.read_valcc_attribute_expect_success(endpoint=endpoint, attribute=attributes.FeatureMap) - - is_ts_feature_supported = feature_map & Clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync - self.step("2b") - if not is_ts_feature_supported: - logging.info("TimeSync feature not supported skipping test case") - - # Skipping all remainig steps - for step in self.get_test_steps(self.current_test_info.name)[self.current_step_index:]: - self.step(step.test_plan_number) - logging.info("Test step skipped") - - return - - else: - logging.info("Test step skipped") + # steps 2a and 2b are handled by the decorator self.step("3a") utcTime = await self.read_single_attribute_check_success(endpoint=0, cluster=Clusters.Objects.TimeSynchronization, attribute=Clusters.TimeSynchronization.Attributes.UTCTime) @@ -103,16 +88,10 @@ async def test_TC_VALCC_4_3(self): if utcTime is not NullValue: logging.info("UTCTime is not null, skipping test case") - # Skipping all remainig steps - for step in self.get_test_steps(self.current_test_info.name)[self.current_step_index:]: - self.step(step.test_plan_number) - logging.info("Test step skipped") - + # Skipping all remaining steps + self.skip_all_remaining_steps(4) return - else: - logging.info("Test step skipped") - self.step(4) try: await self.send_single_cmd(cmd=Clusters.Objects.ValveConfigurationAndControl.Commands.Open(), endpoint=endpoint) diff --git a/src/python_testing/TC_VALCC_4_4.py b/src/python_testing/TC_VALCC_4_4.py index 8915bd9044734e..17189c351e1b4a 100644 --- a/src/python_testing/TC_VALCC_4_4.py +++ b/src/python_testing/TC_VALCC_4_4.py @@ -37,7 +37,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import (MatterBaseTest, TestStep, async_test_body, default_matter_test_main, +from chip.testing.matter_testing import (MatterBaseTest, TestStep, default_matter_test_main, has_feature, run_if_endpoint_matches, utc_time_in_matter_epoch) from mobly import asserts @@ -78,7 +78,7 @@ def pics_TC_VALCC_4_4(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_feature(Clusters.ValveConfigurationAndControl, Clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync)) async def test_TC_VALCC_4_4(self): endpoint = self.get_endpoint(default=1) @@ -87,23 +87,8 @@ async def test_TC_VALCC_4_4(self): attributes = Clusters.ValveConfigurationAndControl.Attributes self.step("2a") - feature_map = await self.read_valcc_attribute_expect_success(endpoint=endpoint, attribute=attributes.FeatureMap) - - is_ts_feature_supported = feature_map & Clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync - self.step("2b") - if not is_ts_feature_supported: - logging.info("TimeSync feature not supported skipping test case") - - # Skipping all remainig steps - for step in self.get_test_steps(self.current_test_info.name)[self.current_step_index:]: - self.step(step.test_plan_number) - logging.info("Test step skipped") - - return - - else: - logging.info("Test step skipped") + # Test steps 2a and 2b are handled by the decorator self.step("3a") utcTime = await self.read_single_attribute_check_success(endpoint=0, cluster=Clusters.Objects.TimeSynchronization, attribute=Clusters.TimeSynchronization.Attributes.UTCTime) @@ -119,7 +104,7 @@ async def test_TC_VALCC_4_4(self): pass else: - logging.info("Test step skipped") + self.mark_current_step_skipped() self.step(4) try: @@ -163,7 +148,7 @@ async def test_TC_VALCC_4_4(self): result = await self.default_controller.WriteAttribute(self.dut_node_id, [(endpoint, attributes.DefaultOpenDuration(defaultOpenDuration))]) asserts.assert_equal(result[0].Status, Status.Success, "DefaultOpenDuration write failed") else: - logging.info("Test step skipped") + self.mark_current_step_skipped() self.step(10) try: diff --git a/src/python_testing/TC_VALCC_4_5.py b/src/python_testing/TC_VALCC_4_5.py index 052e424529ca19..12743338cdf144 100644 --- a/src/python_testing/TC_VALCC_4_5.py +++ b/src/python_testing/TC_VALCC_4_5.py @@ -37,7 +37,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.testing.matter_testing import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -69,7 +69,7 @@ def pics_TC_VALCC_4_5(self) -> list[str]: ] return pics - @async_test_body + @run_if_endpoint_matches(has_cluster(Clusters.ValveConfigurationAndControl)) async def test_TC_VALCC_4_5(self): endpoint = self.get_endpoint(default=1) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index 46c32a2e178b79..9558e5c2ce6424 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -1142,8 +1142,6 @@ def setup_class(self): self.current_step_index = 0 self.step_start_time = datetime.now(timezone.utc) self.step_skipped = False - self.global_wildcard = asyncio.wait_for(self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath( - None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)]), timeout=60) # self.stored_global_wildcard stores value of self.global_wildcard after first async call. # Because setup_class can be called before commissioning, this variable is lazy-initialized # where the read is deferred until the first guard function call that requires global attributes. @@ -1479,6 +1477,13 @@ def pics_guard(self, pics_condition: bool): self.mark_current_step_skipped() return pics_condition + async def _populate_wildcard(self): + """ Populates self.stored_global_wildcard if not already filled. """ + if self.stored_global_wildcard is None: + global_wildcard = asyncio.wait_for(self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath( + None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)]), timeout=60) + self.stored_global_wildcard = await global_wildcard + async def attribute_guard(self, endpoint: int, attribute: ClusterObjects.ClusterAttributeDescriptor): """Similar to pics_guard above, except checks a condition and if False marks the test step as skipped and returns False using attributes against attributes_list, otherwise returns True. @@ -1492,8 +1497,7 @@ async def attribute_guard(self, endpoint: int, attribute: ClusterObjects.Cluster if self.attribute_guard(condition2_needs_to_be_false_to_skip_step): # skip step 2 if condition not met """ - if self.stored_global_wildcard is None: - self.stored_global_wildcard = await self.global_wildcard + await self._populate_wildcard() attr_condition = _has_attribute(wildcard=self.stored_global_wildcard, endpoint=endpoint, attribute=attribute) if not attr_condition: self.mark_current_step_skipped() @@ -1512,8 +1516,7 @@ async def command_guard(self, endpoint: int, command: ClusterObjects.ClusterComm if self.command_guard(condition2_needs_to_be_false_to_skip_step): # skip step 2 if condition not met """ - if self.stored_global_wildcard is None: - self.stored_global_wildcard = await self.global_wildcard + await self._populate_wildcard() cmd_condition = _has_command(wildcard=self.stored_global_wildcard, endpoint=endpoint, command=command) if not cmd_condition: self.mark_current_step_skipped() @@ -1532,8 +1535,7 @@ async def feature_guard(self, endpoint: int, cluster: ClusterObjects.ClusterObje if self.feature_guard(condition2_needs_to_be_false_to_skip_step): # skip step 2 if condition not met """ - if self.stored_global_wildcard is None: - self.stored_global_wildcard = await self.global_wildcard + await self._populate_wildcard() feat_condition = _has_feature(wildcard=self.stored_global_wildcard, endpoint=endpoint, cluster=cluster, feature=feature_int) if not feat_condition: self.mark_current_step_skipped() diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/taglist_and_topology_test.py b/src/python_testing/matter_testing_infrastructure/chip/testing/taglist_and_topology_test.py index eb0ab1289ee183..db37dafc5ba6a3 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/taglist_and_topology_test.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/taglist_and_topology_test.py @@ -18,6 +18,7 @@ import functools from collections import defaultdict from dataclasses import dataclass, field +from chip.clusters.Types import Nullable, NullValue from typing import Any import chip.clusters as Clusters