Skip to content

Commit e054bb8

Browse files
sayondeeppre-commit-ci[bot]pidarped
authored
[AVAnalysis] Server Implementation with Remote Context Detection (project-chip#73360)
* [AVAnalysis] Cleanups: dedupe test mock, guard delegate derefs, remove dead code - Extract duplicated MockAvAnalysisDelegate into tests/MockAvAnalysisDelegate.h - Null-guard mDelegate use in Shutdown() and LoadPersistentAttributes() - Remove stray kMaxSpeakerLevel and unused CameraAvSettingsUserLevelManagement includes - Register AvAnalysisStorage.h in BUILD.gn * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [AVAnalysis] Add fixed-capacity stream table backing the REMCONDETECT attributes - New AnalysisStreamTable: single fixed allocation sized by MaxAnalysisStreamCount, entries keyed by the camera-provided AnalysisStreamID (the VideoStreamID returned by VideoStreamAllocate), created in PendingInitiation state - MaxAnalysisStreamCount becomes a constructor parameter, matching its Fixed quality; Startup() requires it to be non-zero iff RemoteContextDetection and initializes the table; the runtime setter is removed - CurrentAnalysisStreamCount is now derived from the table count and AnalysisStreams encodes from the table, keeping the two attributes consistent by construction * [AVAnalysis] Persist AnalysisStreams and TrackingEnabled * [AVAnalysis] Introduce AvAnalysisCameraClient for camera-facing interactions - Remove unused placeholder stream methods from AvAnalysisDelegate; - Camera stream allocation/deallocation will be performed by the cluster itself through AvAnalysisCameraClient * [AVAnalysis] Add DefaultAvAnalysisCameraClient - AvAnalysisCameraClient: reaches the camera via CASESessionManager and sends VideoStreamAllocate (StreamUsage=Analysis, default constraints) / VideoStreamDeallocate with a CommandSender - Camera response status is propagated to the callback per spec; * [AVAnalysis] Implement EstablishAnalysisStream - The command response waits on the camera's VideoStreamAllocate response; camera status is propagated and EstablishAnalysisStreamResponse carries the camera-assigned stream id - Stream entries record the camera node (survives reboot); persistence format becomes {id, nodeId, fabric} since session state is reset on restore * [AVAnalysis] Implement RemoveAnalysisStream - VideoStreamDeallocate is sent to the camera - On success the entry is removed, counts updated and the table persisted * [AVAnalysis] Fix issues from CI * [AVAnalysis] Make Activate/Deactivate placeholders respond INVALID_IN_STATE Session activation is implemented in a follow-up PR; until then no stream can leave PendingInitiation, so INVALID_IN_STATE is the accurate response rather than a misleading SUCCESS. Tests updated accordingly. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [AVAnalysis] Include feature-conditional Watermark/OSD fields in VideoStreamAllocate Cameras with the Watermark/OSD features require WatermarkEnabled/OSDEnabled to be present in VideoStreamAllocate (feature-conditional conformance) and reject the command otherwise. Add SetCameraVideoTraits() so the application declares the camera's features; discovery from the camera's FeatureMap is a TODO. Found by two-application testing against the Linux camera-app. * [AVAnalysis] Lower default VideoStreamAllocate max bitrate to 2 Mbps * [AVAnalysis] Cancel in-flight camera interactions on cluster shutdown * [AVAnalysis] Gate camera-bound commands on the in-flight interaction The Busy gate used pending-command-handle validity, but the handle is invalidated when the invoking client's exchange closes while the camera interaction is still running; a second command in that window started a second interaction and could be completed with the first one's result. Track the interaction explicitly, answer Busy while it is in flight, and map the camera client's CHIP_ERROR_BUSY to a Busy status. * [AVAnalysis] Release camera streams orphaned by table-insertion failure If VideoStreamAllocate succeeds but the stream table rejects the id, the allocated camera stream had no owner and could never be removed. Request its deallocation before failing the command. * [AVAnalysis] Introduce CameraProfile in the camera client VideoStreamAllocate requests are now built from a per-request CameraProfile * [AVAnalysis] Make camera profile discovery asynchronous Hold the session across a discovery phase between connection and command send * [AVAnalysis] Discover the camera's AVSM endpoint via Descriptor read * [AVAnalysis] Read the camera's AVSM capabilities into the profile After the Descriptor read locates the AVSM endpoint, a second targeted read fetches FeatureMap, VideoSensorParams, RateDistortionTradeOffPoints and SupportedStreamUsages from it. The allocate request bounds now come from the camera: Watermark/OSD traits from the feature bits, maximum resolution and frame rate from the sensor parameters. A camera whose SupportedStreamUsages lacks Analysis fails the request with INVALID_IN_STATE before any allocation is attempted. Fields the camera does not report keep their configured defaults. * [AVAnalysis] Make camera discovery authoritative in the camera client Init no longer takes the camera's AVSM endpoint and SetCameraVideoTraits is removed: the endpoint, the Watermark/OSD traits and the stream constraints all come from the discovery reads. A camera on which no CameraAVStreamManagement endpoint is found now fails the request instead of falling back to a configured endpoint. * [AVAnalysis] Do not deallocate a camera stream on a duplicate stream id * [AVAnalysis] Model the in-flight camera interaction as a state machine * [AVAnalysis] Reconcile the stream table even when the command exchange died The camera's answer is ground truth regardless of whether the invoking client is still listening. A successful allocation whose client exchange timed out was previously never added to the table, leaking the camera stream with no way to remove it; a successful deallocation in the same situation left a permanently stuck table entry. The table mutations now happen unconditionally on a Success completion; only the command response requires the parked handler to still be alive. * [AVAnalysis] Generate AnalysisStreamIDs in the cluster AnalysisStreamIDs are now cluster-generated instead of reusing the camera-assigned VideoStreamID, which is camera-scoped and could collide across cameras. The VideoStreamID is kept internally per entry for the camera-bound commands; it and the id counter persist across reboots. Re-establishing an already-tracked camera stream returns the existing analysis stream id, making retries idempotent. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [AVAnalysis] Re-verify the camera session before each discovery read * [AVAnalysis] Scope the response-delivered flag to one command interaction * [AVAnalysis] List the camera client sources in the CMake build * [AVAnalysis] Answer NOT_FOUND for unknown streams in the Activate stubs * [AVAnalysis] Skip the capabilities read for deallocation requests * [AVAnalysis] Fixes in the camera-interaction completion paths * [AVAnalysis] Name camera stream ids for what they are in the client interface * [AVAnalysis] Allocate the CommandSender through the CHIP platform allocator * [AVAnalysis] Follow the spec on stream ids and the Remove status * Discover camera profile * Fix complete, abort and typos * CI Fix * Address coderabbit review comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pradip De <pradipd@google.com>
1 parent f43bc3a commit e054bb8

18 files changed

Lines changed: 3301 additions & 206 deletions

examples/camera-app/linux/include/clusters/av-analysis/av-analysis-manager.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,6 @@ class AvAnalysisManager : public AvAnalysisDelegate
3838
*/
3939
virtual void ShutdownApp() override;
4040

41-
/**
42-
* Delegate command handlers
43-
*/
44-
45-
/**
46-
*/
47-
virtual Protocols::InteractionModel::Status EstablishAnalysisStream() override;
48-
49-
/**
50-
*/
51-
virtual Protocols::InteractionModel::Status ActivateAnalysisStream() override;
52-
53-
/**
54-
*/
55-
virtual Protocols::InteractionModel::Status DeactivateAnalysisStream() override;
56-
57-
/**
58-
*/
59-
virtual Protocols::InteractionModel::Status RemoveAnalysisStream() override;
60-
6141
/**
6242
* Delegate command assists
6343
*/

examples/camera-app/linux/src/clusters/av-analysis/av-analysis-manager.cpp

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,6 @@ using namespace chip::app::Clusters::AvAnalysis::Structs;
3232
*/
3333
void AvAnalysisManager::ShutdownApp() {}
3434

35-
/**
36-
* Delegate command handlers
37-
*/
38-
39-
/**
40-
*/
41-
Protocols::InteractionModel::Status AvAnalysisManager::EstablishAnalysisStream()
42-
{
43-
return Protocols::InteractionModel::Status::Success;
44-
}
45-
46-
/**
47-
*/
48-
Protocols::InteractionModel::Status AvAnalysisManager::ActivateAnalysisStream()
49-
{
50-
return Protocols::InteractionModel::Status::Success;
51-
}
52-
53-
/**
54-
*/
55-
Protocols::InteractionModel::Status AvAnalysisManager::DeactivateAnalysisStream()
56-
{
57-
return Protocols::InteractionModel::Status::Success;
58-
}
59-
60-
/**
61-
*/
62-
Protocols::InteractionModel::Status AvAnalysisManager::RemoveAnalysisStream()
63-
{
64-
return Protocols::InteractionModel::Status::Success;
65-
}
66-
6735
/**
6836
* Delegate command assists
6937
*/
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
*
3+
* Copyright (c) 2026 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <cstdint>
22+
23+
#include <lib/core/CHIPError.h>
24+
#include <lib/core/ScopedNodeId.h>
25+
#include <protocols/interaction_model/StatusCode.h>
26+
27+
namespace chip {
28+
namespace app {
29+
namespace Clusters {
30+
31+
/**
32+
* Camera-facing client interactions performed by an AV Analysis server with the RemoteContextDetection
33+
* feature: allocating and deallocating analysis video streams on the camera's CameraAVStreamManagement cluster.
34+
*
35+
* The interface exists so unit tests can substitute the callbacks directly.
36+
*
37+
* All methods are asynchronous: a CHIP_NO_ERROR return means the interaction was started and its outcome
38+
* will be reported exactly once through the per-request Callback. A failing return means the interaction
39+
* could not be started and no callback will occur. Callbacks are always delivered on the Matter thread.
40+
*/
41+
class AvAnalysisCameraClient
42+
{
43+
public:
44+
class Callback
45+
{
46+
public:
47+
virtual ~Callback() = default;
48+
49+
/**
50+
* Outcome of RequestVideoStreamAllocation. On Status::Success, aVideoStreamId is the camera-assigned
51+
* VideoStreamID of the allocated stream; Any other status is the camera's response status, to be propagated as the
52+
* EstablishAnalysisStream command status.
53+
*/
54+
virtual void OnVideoStreamAllocated(Protocols::InteractionModel::Status aStatus, uint16_t aVideoStreamId) = 0;
55+
56+
/**
57+
* Outcome of RequestVideoStreamDeallocation for the camera stream aVideoStreamId. A non-Success
58+
* status is the camera's response status, to be propagated as the RemoveAnalysisStream command status.
59+
*/
60+
virtual void OnVideoStreamDeallocated(Protocols::InteractionModel::Status aStatus, uint16_t aVideoStreamId) = 0;
61+
};
62+
63+
virtual ~AvAnalysisCameraClient() = default;
64+
65+
/**
66+
* Sends VideoStreamAllocate with StreamUsage Analysis to the CameraAVStreamManagement cluster on the
67+
* given camera node; remaining allocate fields are implementation defined.
68+
*
69+
* @param aCameraNode The camera, on the same fabric as this Analysis Node, as identified by the
70+
* EstablishAnalysisStream NodeID field and the invoking client's fabric.
71+
* @param aCallback Receives the outcome; must outlive the request.
72+
*/
73+
virtual CHIP_ERROR RequestVideoStreamAllocation(const ScopedNodeId & aCameraNode, Callback & aCallback) = 0;
74+
75+
/**
76+
* Sends VideoStreamDeallocate for the given camera stream to the given camera node.
77+
*
78+
* @param aCameraNode The camera the stream was allocated on.
79+
* @param aVideoStreamId The camera-assigned VideoStreamID to deallocate, as reported by
80+
* OnVideoStreamAllocated.
81+
* @param aCallback Receives the outcome; must outlive the request.
82+
*/
83+
virtual CHIP_ERROR RequestVideoStreamDeallocation(const ScopedNodeId & aCameraNode, uint16_t aVideoStreamId,
84+
Callback & aCallback) = 0;
85+
86+
/**
87+
* Abandons any in-flight request; its callback is never delivered. Safe to call from within a
88+
* completion callback: the request is already finished by then, making this a no-op.
89+
*/
90+
virtual void Cancel() = 0;
91+
};
92+
93+
} // namespace Clusters
94+
} // namespace app
95+
} // namespace chip
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
*
3+
* Copyright (c) 2026 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <app/CommandHandler.h>
22+
#include <app/ConcreteCommandPath.h>
23+
#include <lib/core/ScopedNodeId.h>
24+
25+
namespace chip {
26+
namespace app {
27+
namespace Clusters {
28+
namespace AvAnalysis {
29+
30+
/**
31+
* A camera-bound command (EstablishAnalysisStream, RemoveAnalysisStream) cannot be answered until
32+
* the camera responds, so its CommandHandler is parked here and the interaction advances through
33+
* this state machine
34+
* Exactly one interaction exists at a time; a command arriving while InFlight() is answered Busy.
35+
*/
36+
class CameraInteraction
37+
{
38+
public:
39+
enum class State : uint8_t
40+
{
41+
kIdle, // No camera interaction in flight
42+
kEstablishing, // VideoStreamAllocate awaiting the camera's answer
43+
kRemoving, // VideoStreamDeallocate awaiting the camera's answer
44+
};
45+
46+
State GetState() const { return mState; }
47+
bool InFlight() const { return mState != State::kIdle; }
48+
49+
/**
50+
* The camera the in-flight interaction is bound to.
51+
*/
52+
const ScopedNodeId & CameraNode() const { return mCameraNode; }
53+
54+
/**
55+
* The AnalysisStreamID the in-flight interaction is about
56+
*/
57+
uint16_t AnalysisStreamId() const { return mAnalysisStreamId; }
58+
59+
/**
60+
* Enters aState, parking the command so it can be answered when the camera responds.
61+
*/
62+
void Begin(State aState, CommandHandler & aHandler, const ConcreteCommandPath & aPath, const ScopedNodeId & aCameraNode,
63+
uint16_t aAnalysisStreamId = 0)
64+
{
65+
mState = aState;
66+
mHandle = CommandHandler::Handle(&aHandler);
67+
mPath = aPath;
68+
mCameraNode = aCameraNode;
69+
mAnalysisStreamId = aAnalysisStreamId;
70+
aHandler.FlushAcksRightAwayOnSlowCommand();
71+
}
72+
73+
/**
74+
* Rolls back a Begin whose camera request could not be started
75+
*/
76+
void Abort()
77+
{
78+
mState = State::kIdle;
79+
mHandle = CommandHandler::Handle();
80+
ClearInteractionState();
81+
}
82+
83+
/**
84+
* Ends the interaction: returns the parked handle. The camera node and stream id are cleared,
85+
* so callers needing them must read them before completing.
86+
*/
87+
CommandHandler::Handle Complete(ConcreteCommandPath & outPath)
88+
{
89+
outPath = mPath;
90+
mState = State::kIdle;
91+
ClearInteractionState();
92+
return std::move(mHandle);
93+
}
94+
95+
private:
96+
void ClearInteractionState()
97+
{
98+
mAnalysisStreamId = 0;
99+
mCameraNode = ScopedNodeId();
100+
mPath = ConcreteCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
101+
}
102+
103+
State mState = State::kIdle;
104+
uint16_t mAnalysisStreamId = 0;
105+
CommandHandler::Handle mHandle;
106+
ConcreteCommandPath mPath = ConcreteCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
107+
ScopedNodeId mCameraNode;
108+
};
109+
110+
} // namespace AvAnalysis
111+
} // namespace Clusters
112+
} // namespace app
113+
} // namespace chip

src/app/clusters/av-analysis-server/AvAnalysisCluster.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
#include <app/reporting/reporting.h>
2121
#include <app/server-cluster/AttributeListBuilder.h>
2222
#include <app/util/util.h>
23-
#include <clusters/CameraAvSettingsUserLevelManagement/Commands.h>
24-
#include <clusters/CameraAvSettingsUserLevelManagement/Ids.h>
25-
#include <clusters/CameraAvSettingsUserLevelManagement/Metadata.h>
2623
#include <lib/core/CHIPSafeCasts.h>
2724
#include <lib/support/DefaultStorageKeyAllocator.h>
2825
#include <protocols/interaction_model/StatusCode.h>
@@ -40,6 +37,11 @@ CHIP_ERROR AvAnalysisCluster::AcceptedCommands(const ConcreteClusterPath & path,
4037
return mLogic.AcceptedCommands(builder);
4138
}
4239

40+
CHIP_ERROR AvAnalysisCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder)
41+
{
42+
return mLogic.GeneratedCommands(builder);
43+
}
44+
4345
CHIP_ERROR AvAnalysisCluster::Attributes(const ConcreteClusterPath & path,
4446
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
4547
{
@@ -68,7 +70,7 @@ DataModel::ActionReturnStatus AvAnalysisCluster::ReadAttribute(const DataModel::
6870
case Attributes::MaxAnalysisStreamCount::Id:
6971
return aEncoder.Encode(mLogic.mMaxAnalysisStreamCount);
7072
case Attributes::CurrentAnalysisStreamCount::Id:
71-
return aEncoder.Encode(mLogic.mCurrentAnalysisStreamCount);
73+
return aEncoder.Encode(mLogic.GetCurrentAnalysisStreamCount());
7274
case Attributes::AnalysisStreams::Id:
7375
return mLogic.ReadAndEncodeAnalysisStreams(aEncoder);
7476
case Attributes::TrackingEnabled::Id:
@@ -97,11 +99,6 @@ DataModel::ActionReturnStatus AvAnalysisCluster::WriteAttribute(const DataModel:
9799
}
98100
}
99101

100-
CHIP_ERROR AvAnalysisCluster::SetMaxAnalysisStreamCount(uint8_t aMaxAnalysisStreamCount)
101-
{
102-
return mLogic.SetMaxAnalysisStreamCount(aMaxAnalysisStreamCount);
103-
}
104-
105102
std::optional<DataModel::ActionReturnStatus> AvAnalysisCluster::InvokeCommand(const DataModel::InvokeRequest & request,
106103
chip::TLV::TLVReader & input_arguments,
107104
CommandHandler * handler)

0 commit comments

Comments
 (0)