blob: 9131e1e68557d5a6c2888097f31d13c1a985db6b [file]
/*
* Copyright (c) 2026 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 <app/clusters/audio-control-server/AudioControlDelegate.h>
#include <app/clusters/scenes-server/SceneHandlerImpl.h>
#include <app/data-model/Nullable.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/OptionalAttributeSet.h>
#include <clusters/AudioControl/Enums.h>
#include <clusters/AudioControl/Metadata.h>
#include <lib/support/BitFlags.h>
#include <optional>
#include <protocols/interaction_model/StatusCode.h>
namespace chip::app::Clusters {
class AudioControlCluster : public DefaultServerCluster, public scenes::DefaultSceneHandlerImpl
{
public:
/// Bitset of optional attributes that can be individually enabled.
/// MinCorrection/MaxCorrection are controlled solely by the feature map, not this set.
using OptionalAttributeSet =
app::OptionalAttributeSet<AudioControl::Attributes::PhysicallyMuted::Id, AudioControl::Attributes::MaxDeviceVolumeDB::Id,
AudioControl::Attributes::MaxUserVolume::Id, AudioControl::Attributes::StartUpMuted::Id,
AudioControl::Attributes::StartUpVolume::Id, AudioControl::Attributes::Bass::Id,
AudioControl::Attributes::Mid::Id, AudioControl::Attributes::Treble::Id>;
class Config
{
public:
Config & WithFeatures(BitFlags<AudioControl::Feature> features)
{
mFeatures = features;
return *this;
}
Config & WithOptionalAttributes(OptionalAttributeSet attrs)
{
mOptionalAttributeSet = attrs;
return *this;
}
Config & WithInitialSoftMuted(bool value)
{
mSoftMuted = value;
return *this;
}
Config & WithInitialPhysicallyMuted(bool value)
{
mPhysicallyMuted = value;
return *this;
}
Config & WithInitialVolume(uint16_t value)
{
mVolume = value;
return *this;
}
Config & WithInitialMaxUserVolume(uint16_t value)
{
mMaxUserVolume = value;
return *this;
}
Config & WithInitialDefaultStepSize(uint16_t value)
{
mDefaultStepSize = value;
return *this;
}
Config & WithSetVolumeUnmutePolicy(AudioControl::UnmutePolicyEnum value)
{
mSetVolumeUnmutePolicy = value;
return *this;
}
Config & WithIncreaseVolumeUnmutePolicy(AudioControl::UnmutePolicyEnum value)
{
mIncreaseVolumeUnmutePolicy = value;
return *this;
}
Config & WithIncreaseVolumeUnmuteVolume(AudioControl::UnmuteVolumeEnum value)
{
mIncreaseVolumeUnmuteVolume = value;
return *this;
}
Config & WithDecreaseVolumeUnmutePolicy(AudioControl::UnmutePolicyEnum value)
{
mDecreaseVolumeUnmutePolicy = value;
return *this;
}
Config & WithInitialStartUpMuted(DataModel::Nullable<bool> value)
{
mStartUpMuted = value;
return *this;
}
Config & WithInitialStartUpVolume(DataModel::Nullable<uint16_t> value)
{
mStartUpVolume = value;
return *this;
}
Config & WithInitialBass(int16_t value)
{
mBass = value;
return *this;
}
Config & WithInitialMid(int16_t value)
{
mMid = value;
return *this;
}
Config & WithInitialTreble(int16_t value)
{
mTreble = value;
return *this;
}
private:
friend class AudioControlCluster;
BitFlags<AudioControl::Feature> mFeatures;
OptionalAttributeSet mOptionalAttributeSet;
// Fixed hardware limits (MinDeviceVolume, MaxDeviceVolume, MaxDeviceVolumeDB,
// MinCorrection, MaxCorrection) are supplied by AudioControlDelegate.
bool mSoftMuted = false;
bool mPhysicallyMuted = false;
uint16_t mVolume = 1;
uint16_t mMaxUserVolume = 100;
uint16_t mDefaultStepSize = 1;
AudioControl::UnmutePolicyEnum mSetVolumeUnmutePolicy = AudioControl::UnmutePolicyEnum::kUnmuteOrChangeVolume;
AudioControl::UnmutePolicyEnum mIncreaseVolumeUnmutePolicy = AudioControl::UnmutePolicyEnum::kUnmuteOrChangeVolume;
AudioControl::UnmuteVolumeEnum mIncreaseVolumeUnmuteVolume = AudioControl::UnmuteVolumeEnum::kVolumePlusStepSize;
AudioControl::UnmutePolicyEnum mDecreaseVolumeUnmutePolicy = AudioControl::UnmutePolicyEnum::kUnmuteOrChangeVolume;
DataModel::Nullable<bool> mStartUpMuted;
DataModel::Nullable<uint16_t> mStartUpVolume;
int16_t mBass = 0;
int16_t mMid = 0;
int16_t mTreble = 0;
};
AudioControlCluster(EndpointId endpointId, AudioControlDelegate & delegate, const Config & config);
// ServerClusterInterface overrides
CHIP_ERROR Startup(ServerClusterContext & context) override;
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override;
DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder) override;
CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override;
CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override;
std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
chip::TLV::TLVReader & input_arguments,
CommandHandler * handler) override;
// scenes::SceneHandler overrides. Registered with the scene table (only when a Scenes
// Management cluster is present on the endpoint) by the CodegenIntegration layer. The
// scene-able attributes are SoftMuted, Volume and, when the BasicEqualizer feature and the
// corresponding band attribute are enabled, Bass/Mid/Treble (AudioControl.adoc "Scene
// Table Extensions"). Transition time is ignored: the stored values are applied at once.
// ApplyScene routes through the delegate (HandleVolumeAndMuteChange / HandleBassChanged /
// ...) before committing, like the command handlers, so a scene recall reaches hardware and
// any coordinating clusters. Stored values outside the current constraints are saturated to
// the closest legal value rather than rejected, so a scene captured before MaxUserVolume or
// the correction range shrank still recalls.
bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override;
CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) override;
CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
scenes::TransitionTimeMs timeMs) override;
// Application-facing API — call these from application code (e.g. hardware event handlers).
// SetSoftMuted/SetVolume/SetPhysicallyMuted/SetBass/SetMid/SetTreble reflect hardware-initiated
// changes and do NOT call the delegate. SetMaxUserVolume is app-initiated and DOES call
// HandleVolumeAndMuteChange when Volume is clipped.
CHIP_ERROR SetSoftMuted(bool softMuted);
CHIP_ERROR SetPhysicallyMuted(bool physicallyMuted);
CHIP_ERROR SetVolume(uint16_t volume);
CHIP_ERROR SetMaxUserVolume(uint16_t maxUserVolume);
CHIP_ERROR SetBass(int16_t bass);
CHIP_ERROR SetMid(int16_t mid);
CHIP_ERROR SetTreble(int16_t treble);
bool GetSoftMuted() const { return mSoftMuted; }
bool GetPhysicallyMuted() const { return mPhysicallyMuted; }
uint16_t GetVolume() const { return mVolume; }
uint16_t GetMaxUserVolume() const { return mMaxUserVolume; }
uint16_t GetMinDeviceVolume() const { return mMinDeviceVolume; }
uint16_t GetMaxDeviceVolume() const { return mMaxDeviceVolume; }
int16_t GetBass() const { return mBass; }
int16_t GetMid() const { return mMid; }
int16_t GetTreble() const { return mTreble; }
private:
AudioControlDelegate & mDelegate;
const BitFlags<AudioControl::Feature> mFeatures;
const OptionalAttributeSet mOptionalAttributeSet;
// Fixed (construction-time) attributes
const uint16_t mMinDeviceVolume;
const uint16_t mMaxDeviceVolume;
const uint16_t mMaxDeviceVolumeDB;
const int16_t mMinCorrection;
const int16_t mMaxCorrection;
// Mutable attributes
bool mSoftMuted = false;
bool mPhysicallyMuted = false;
uint16_t mVolume;
uint16_t mMaxUserVolume;
uint16_t mDefaultStepSize;
AudioControl::UnmutePolicyEnum mSetVolumeUnmutePolicy;
AudioControl::UnmutePolicyEnum mIncreaseVolumeUnmutePolicy;
AudioControl::UnmuteVolumeEnum mIncreaseVolumeUnmuteVolume;
AudioControl::UnmutePolicyEnum mDecreaseVolumeUnmutePolicy;
DataModel::Nullable<bool> mStartUpMuted;
DataModel::Nullable<uint16_t> mStartUpVolume;
int16_t mBass;
int16_t mMid;
int16_t mTreble;
// Helpers
uint16_t EffectiveMaxVolume() const;
void StoreVolume();
void StoreSoftMuted();
void StoreStartUpMuted();
void StoreStartUpVolume();
void StoreDefaultStepSize();
void StoreSetVolumeUnmutePolicy();
void StoreIncreaseVolumeUnmutePolicy();
void StoreIncreaseVolumeUnmuteVolume();
void StoreDecreaseVolumeUnmutePolicy();
void StoreMaxUserVolume();
void StoreBass();
void StoreMid();
void StoreTreble();
// Command handlers — one per accepted command.
DataModel::ActionReturnStatus HandleMute();
DataModel::ActionReturnStatus HandleUnmute();
DataModel::ActionReturnStatus HandleToggleMuted();
DataModel::ActionReturnStatus HandleSetVolume(chip::TLV::TLVReader & input_arguments);
DataModel::ActionReturnStatus HandleIncreaseVolume(chip::TLV::TLVReader & input_arguments);
DataModel::ActionReturnStatus HandleDecreaseVolume(chip::TLV::TLVReader & input_arguments);
// Applies the UnmutePolicy table: calls delegate with computed (newVolume, newSoftMuted),
// then updates cluster state only if the delegate succeeds.
Protocols::InteractionModel::Status ApplyUnmutePolicy(AudioControl::UnmutePolicyEnum policy, uint16_t requestedVolume);
};
} // namespace chip::app::Clusters