Skip to content

Backend Development

The backend is a native C++17 application built with CMake. It provides runtime services, media processing, NN inference, API routing, event output, and scenario task orchestration — all linked into a single executable: cosmo-engine.

Architecture Overview

The CMake build produces object libraries organized in layers. The final binary links them together:

Layer 0 (Foundation)    cosmo_util  cosmo_db  cosmo_platform  cosmo_mem
Layer 1 (Infrastructure)  cosmo_media  cosmo_infer  cosmo_network  cosmo_nn
Layer 2 (Business)      cosmo_flow  cosmo_service  cosmo_linkage
Layer 3 (Interface)     cosmo_api

Each object library maps to a source directory under src/. The layer order ensures that higher layers can depend on lower ones, but not vice versa.

Build System

Entry Points

Script / FilePurpose
scripts/build_cpu.shx86 CPU backend build
scripts/build_cpu_test.shCPU test build (cosmo-tests)
scripts/build.shSophon / aarch64 build
CMakeLists.txtRoot build and packaging

CMake Options

OptionDefaultDescription
COSMO_TARGET_ARCHaarch64Target architecture: aarch64 or x86_64
COSMO_NN_USE_SOPHON_BACKENDONEnable Sophon TPU backend
COSMO_NN_USE_CPU_BACKENDOFFEnable CPU / ONNX Runtime backend
COSMO_ENABLE_OPENH264autoEnable OpenH264 (ON when CPU backend selected)
COSMO_DEV_MODEOFFDisable watchdog and other production guards
COSMO_MODEL_GUARDautoLink libcosmo_model_guard.so (Sophon default)
BUILD_TESTSOFFBuild cosmo-tests with Catch2 + gcov

COSMO_NN_USE_SOPHON_BACKEND and COSMO_NN_USE_CPU_BACKEND are mutually exclusive. Selecting CPU auto-enables COSMO_ENABLE_OPENH264 and disables COSMO_MODEL_GUARD.

Service Layer & Dependency Injection

ServiceRegistry

All backend services are wired through cosmo::service::ServiceRegistry, a thread-safe DI container keyed by std::type_index.

cpp
// Production registration (owning — destroyed in reverse order on ShutdownAll)
ServiceRegistry::Instance().Register<IFooService>(std::make_unique<FooServiceImpl>());

// Test / mock injection (non-owning)
ServiceRegistry::Instance().Set<IFooService>(&mockFoo);

// Resolution
auto& foo = ServiceRegistry::Instance().Get<IFooService>();

Startup Sequence

Service initialization happens in src/app/app_init.cc, called from SwDeviceInit(), in four phases:

Phase 1 — RegisterInfrastructureServices() Registers foundational services: IFileService, IEventNotifier (WebSocket), IMemoryPoolService, IStorageCleanService, IWatchDogService, IDbService (SQLite), IOsdTextRenderer, IVideoFrameService, ITaskService, IInferPoolService, ILlmInferService, INetworkService, IDeviceDiscoveryService, IHttpClient.

Phase 2 — RegisterBusinessServices() Registers domain services: audio, linkage, camera, picture tasks, algorithms, device info, time (NTP), system config, model management, alarm records/push, auth, schedules, face/body/item libraries, live stream, actions, client messages, app info, timer restart.

Phase 3 — InitializeServices() Calls Init() on services in dependency order — OSD fonts, algorithm service, network service, database, alarms, models, face data, camera entities, app info, MQTT start.

Phase 4 — InitializeExternalComponents() Starts the HTTP server, WebSocket server, device discovery multicast, storage cleanup timer, and hardware watchdog (skipped in COSMO_DEV_MODE).

Adding a New Service

  1. Define an interface in the appropriate src/service/<domain>/ subdirectory, inheriting from a base interface if one exists for the domain.
  2. Implement the service class in the same domain directory.
  3. Register it in the correct phase in src/app/app_init.cc:
    cpp
    ServiceRegistry::Instance().Register<IMyService>(std::make_unique<MyServiceImpl>());
    If the service exposes multiple narrow interfaces, use Set<>() for additional aliases:
    cpp
    ServiceRegistry::Instance().Set<IMyQuery>(&ServiceRegistry::Instance().Get<IMyService>());
  4. Add the implementation file to src/service/CMakeLists.txt.

Tear-down is automatic — SwDeviceDestroy() calls ServiceRegistry::ShutdownAll(), which destroys owned services in reverse registration order.

API Routing

Route Registration

API routes are defined in src/api/ApiRouter.cc and src/api/ApiRouterRoutes.cc. Two ApiRouter instances are created: one for HTTP requests (MessageFromHttp) and one for MQTT-dispatched requests (MessageFromMqtt). Both share the same route table.

Routes use a macro pattern (defined in ApiRouterInternal.h):

cpp
ROUTE("/gtw/cwai/System/", kAuth, system_handler_, System, QueryDeviceInfo);

The macro concatenates the path prefix with its final X argument. The example therefore registers /gtw/cwai/System/QueryDeviceInfo. Do not pass the complete endpoint as the first argument, or QueryDeviceInfo will be appended a second time.

This expands to a dispatch that:

  1. Registers and matches the complete URL case-insensitively through util::ToLower.
  2. Validates the HTTP mtk for kAuth; kNoAuth permits anonymous access.
  3. Deserializes the request JSON into NS::MsgQueryDeviceInfoSend.
  4. Calls the handler.
  5. Serializes the response (NS::MsgQueryDeviceInfoRecv) back to JSON.

Use ROUTE_CONTEXT when the handler needs request context such as the upload-session body or authenticated principal. Core DTOs use ROUTE_CORE or ROUTE_CORE_CONTEXT. These macros also take a path prefix, not a full endpoint.

Standard Response Envelope

Most management responses inherit from MsgSendHead:

FieldTypeNotes
resCodenumber1 = success, 0 = failure
resMsgobject[]Error / info message list
resultCodestringCompatibility response code
resultMsgstringCompatibility response text

MsgSendHead itself does not declare resData; concrete response DTOs define their business payload outside the common response head.

Adding a New Route Group

  1. Create a message handler class under src/api/ (e.g., MessageMyHandler.h/.cc).
  2. Define the Send/Recv structs and the handler method.
  3. Add a RegisterMyRoutes() function that calls ROUTE or ROUTE_CORE for each endpoint.
  4. Call RegisterMyRoutes() from the ApiRouter constructor.

Route Groups

Registration FunctionURL PrefixDomain
RegisterCoreRoutes()/v1/cwai/aihost/, /gtw/cwai/aihost/, /gtw/cwai/login/Core, compatibility, login
RegisterNetworkRoutes()/gtw/cwai/network/Network, DNS, NTP
RegisterAlgorithmRoutes()/gtw/cwai/Algorithm/, /gtw/cwai/algorithm/layout/Algorithms, orchestration
RegisterModelRoutes()/gtw/cwai/atomic/Model/Model repository
RegisterScheduleRoutes()/gtw/cwai/schedule/Time templates
RegisterEventRoutes()/gtw/cwai/Event/Events, alarms
RegisterCameraRoutes()/gtw/cwai/Camera/Cameras, USB
RegisterTaskRoutes()/gtw/cwai/Task/Scenario tasks
RegisterSystemRoutes()/gtw/cwai/System/Device, upgrade, diag
RegisterLibraryRoutes()/gtw/cwai/Library/, /BodyLibrary/, /ThingsLibrary/Face, body, item libraries
RegisterFileRoutes()/gtw/cwai/File/File import
RegisterAudioRoutes()/gtw/cwai/Audio/Audio files, devices
RegisterLinkageRoutes()/gtw/cwai/AlarmStrage/Alarm linkage
RegisterLiveStreamRoutes()/gtw/cwai/LiveStream/Live stream lifecycle
RegisterOnboardingRoutes()/gtw/cwai/Onboarding/First-use guide

Testing

Framework

Tests use Catch2 with Trompeloeil for mocking. Test files live under test/; reusable service mocks live under test/mock/.

Build and Run

bash
bash scripts/build_cpu_test.sh
./build_cpu/cosmo-tests

Mocking

The ServiceRegistry::Set<T>(ptr) non-owning injection is the primary mechanism for replacing real services with mocks in tests. Common interfaces have focused test/mock/Mock*Service.h files, while test/mock/MockServiceRegistry.h and .cc register the commonly shared mocks in the test ServiceRegistry.

cpp
#include "mock/MockServiceRegistry.h"

cosmo::test::MockServiceRegistry mocks;

When adding a service, add a narrow mock for its interface. Add it to the MockServiceRegistry aggregate only when multiple tests need the same dependency assembly.

Test File Naming

Test files follow the pattern test_<component>.cc, e.g.:

  • test_service_registry.cc → tests ServiceRegistry
  • test_video_frame_service_impl.cc → tests VideoFrameServiceImpl
  • test_api_router.cc → tests ApiRouter

When adding a service, add focused behavior tests and Trompeloeil mocks for dependencies that need isolation.

Code Style

The project follows the Google C++ Style Guide adapted for C++17. Full details are in CODING_STYLE.md at the repository root (Chinese). Key conventions:

CategoryConventionExample
File namingPascalCase, .h / .ccVideoFrameService.h
Header guard#pragma once
Namespacecosmo:: top-level; per-module sub-nscosmo::media, cosmo::flow
ClassesPascalCaseVideoFrameServiceImpl
Member variablessnake_case_ (trailing underscore)video_width_
Struct memberssnake_case (no underscore)max_retries
FunctionsPascalCaseGetVideoFrame()
Constantsk + PascalCasekDefaultHttpPort
Enumsenum class with k + PascalCasekAlarm, kInfo
Booleansis_ / has_ / should_ prefixis_running_

Strict rules:

  • No using namespace in headers.
  • No bare new / delete — use RAII and smart pointers.
  • No C-style casts — use static_cast, const_cast, etc.
  • No #define for constants — use constexpr.
  • No magic numbers.
  • const correctness is required everywhere.
  • Thread safety: use std::atomic for shared flags, lock guards for shared data.

Formatting and Static Analysis

bash
# Format check
bash scripts/format_check.sh --check         # all src/ + test/
bash scripts/format_check.sh --staged --check # staged only
bash scripts/format_check.sh --fix            # auto-format

# Static analysis
bash scripts/static_analysis.sh --cppcheck
bash scripts/static_analysis.sh --clang-tidy  # needs compile_commands.json

Configuration files at the repository root: .clang-format, .clang-tidy, .cppcheck-suppressions.

Key Modules Reference

Flow (src/flow/) — Algorithm Pipeline Nodes

The flow layer implements the composable nodes that make up scenario task pipelines:

SubdirectoryPurpose
action/Action instances, branches, the action manager
alarm/Area alarms, task alarms, alarm suppression
channel/Algorithm channel decode, demux, MP4 record, buffer pool
classify/AI classifiers (attribute, group, area)
detect/AI detectors (YOLO), DINO open-vocabulary detector, SAM2 segmenter
logical/Logical judgment, face logic, sensitivity calculation
qwen3vl/Qwen3 VL inference worker and manager
recognizer/AI recognizers (face/body re-id)
stream/RTMP streaming, encoder, overview renderer
common/Shared types: AlgDataUnit, AlgDataQueue, AlgCommonType

Each domain typically has:

  • An Ai* class (e.g., AiDetector) — the actual algorithm logic.
  • A P* class (e.g., PDetector) — the pipeline node wrapping it, forming part of an AlgActionBase-derived action.

NN (src/nn/) — Backend Abstraction

The neural network layer uses an abstract device pattern:

  • src/nn/core/ — Device-agnostic graph, blob, and node base classes.
  • src/nn/device/sophon/ — Sophon BM1688 TPU backend (BMRT).
  • src/nn/device/cpu/ — x86 ONNX Runtime backend.
  • src/nn/device/naive/ — Fallback for in-memory compute.
  • src/nn/pipeline/ — High-level pipelines: detection, classify, feature, keypoints, advanced.

Adding a new NN op requires a node implementation for each active backend.

Inference (src/infer/) — Backend-Agnostic Inference

Uses a "Unify" pattern: each task type has an interface + a unified implementation that delegates to the active NN backend:

ClassTask
AiDetectorUnifyObject detection
AiClassifierUnifyImage classification
AiRecognizerUnifyRe-identification (embeddings)
AiLandmarkerUnifyKeypoint / landmark
AiTrackerUnifyObject tracking (ByteTrack)
DinoDetectorUnifyOpen-vocabulary detection
Sam2SegmenterUnifySAM2 segmentation
Qwen3VLUnifyVision-language model

Media (src/media/) — Video / Audio / OSD

Handles video decode/encode (FFmpeg), hardware codecs (Sophon VPP/VPU), OSD rendering (bitmap text + graphics), frame transform, and streaming output.

Network (src/network/) — HTTP / MQTT

SubdirectoryLibraryPurpose
http/uWebSocketsHTTP server, thread pool, multipart parsing
mqtt/Paho MQTT CMQTT client, topics, heartbeats
msg/Internal message dispatch

Database (src/db/) — SQLite

The DaoBase class wraps SQLite::Database and provides SetCondition(), SetLimit(), Begin(), Commit(), Rollback(). Concrete DAOs (e.g., PersonDao, TaskEventDao, PassengerFlowDao) extend it. TransactionGuard provides RAII transaction management.

Third-Party Dependencies

Key third-party libraries (under 3rd/ and linked via CMake ExternalProject):

LibraryPurposeLicense
SQLiteCppSQLite C++ wrapperMIT
fmtString formattingMIT
nlohmann-jsonJSON parsingMIT
uWebSocketsHTTP / WebSocket serverApache 2.0
Paho MQTT CMQTT clientEPL 2.0
ONNX Runtimex86 CPU inferenceMIT
Sophon BMRTaarch64 TPU inferenceProprietary
FFmpegVideo decode / encodeLGPL 2.1+
OpenH264H.264 encode (CPU backend)BSD 2-Clause
Catch2Test frameworkBoost
TrompeloeilMocking frameworkBoost

Released under the Apache 2.0 License.