Chapter 7: Common Data Model Bridge (CDMB)
In Chapter 4 we saw how the MW Services layer manages the hub's middleware concerns. Now we step into the control plane — the part of the SDK that actually talks to smart-home devices. The first stop is the Common Data Model Bridge, or CDMB.
What Does the CDMB Do?
Think of the CDMB as a mail sorting office. The cloud-side Agent sends a JSON command — "turn on the Z-Wave light in the living room" — and the CDMB's job is to:
- Open the envelope (parse the JSON).
- Read the address label (inspect the
eNIdprefix). - Drop the letter into the right outgoing mailbag (route to the correct protocol plugin).
It never speaks Z-Wave or Zigbee itself; it only knows who speaks those languages and hands the work off.
Source orientation: All CDMB code lives under
control/IoTSmartHomeDevice-CDMBCore/. Headers are ininclude/, the entry point issrc/iotshd_cdmb_main.c, and the core logic is insrc/core/iotshd_cdmb_svc.c.
The Routing Key: eNId Prefixes
Every managed device has an endpoint Node ID (eNId) that starts with a short protocol prefix. The CDMB uses a simple strncmp against the first three characters to decide where a command goes:
| Prefix | Protocol | Compile Flag |
|---|---|---|
zw. |
Z-Wave | ZWAVE_SUPPORT_ENABLED |
zb. |
Zigbee | ZIGBEE_SUPPORT_ENABLED |
mt. |
Matter | MATTER_SUPPORT_ENABLED (reserved) |
The lookup happens in iotmiCdmb_retrieveDeviceProtocol():
// src/core/iotshd_cdmb_svc.c
if (!strncmp(node_id->valuestring, "zw.", 3)) {
protocol = PROTOCOL_ZWAVE;
} else if (!strncmp(node_id->valuestring, "zb.", 3)) {
protocol = PROTOCOL_ZIGBEE;
} else if (!strncmp(node_id->valuestring, "mt.", 3)) {
protocol = PROTOCOL_MATTER;
}
There is also a custom plugin path: if the payload contains a customProtocolClientName field instead of a standard prefix, the CDMB routes to PROTOCOL_CUSTOM. We will cover custom plugins in detail in Chapter 8.
JSON Message Format
Every request the Agent sends over IPC follows a standard envelope. The CDMB parses six required header fields before it ever looks at the payload:
{
"mTp": "controlCommand",
"ve": "1.0.0",
"t": "1713100000.000",
"mId": "msg-abc-123",
"tId": "managed-thing-42",
"cId": "client-007",
"p": { "eNId": "zw.5", "...": "..." }
}
These map to the iotmiCdmb_requestHeaderItem_t enum defined in include/iotshd_cdmb_common_types.h:
typedef enum {
REQ_MESSAGETYPE = 0, // "mTp"
REQ_VERSION, // "ve"
REQ_TIMESTAMP, // "t"
REQ_MESSAGEID, // "mId"
REQ_MANAGEDTHINGID, // "tId"
REQ_CLIENTID // "cId"
} iotmiCdmb_requestHeaderItem_t;
The header parser (iotmiCdmb_requestHeaderParser) rejects the request immediately if any of these six fields is missing — fail fast, no guessing.
How a Command Flows Through the CDMB
Here is the end-to-end journey of a single control command:
sequenceDiagram
participant Agent
participant IPC as IPC Framework
participant CDMB
participant Router as Protocol Router
participant Plugin as Z-Wave Plugin
Agent->>IPC: JSON command (async)
IPC->>CDMB: iotmiCdmb_requestHandler()
CDMB->>Router: parse eNId → "zw."
Router->>Plugin: IotmiCdmbZWave_TaskHandler(task)
Plugin-->>CDMB: response payload
Let's walk through each step in the code.
Step 1 — IPC Delivers the Request
The IPC framework calls iotmiCdmb_requestHandler() with command type IOTMI_IPC_SVC_SEND_REQ_TO_CDMB. This is the single entry point for all Agent-to-CDMB traffic (see Chapter 3 for how IPC dispatches commands).
// src/core/iotshd_cdmb_svc.c
iotmi_statusCode_t iotmiCdmb_requestHandler(
IotmiIpc_Command cmd, uint32_t buf_len,
const uint8_t *in_buf,
uint32_t *ret_buf_len, uint8_t **ret_buf);
Step 2 — Parse and Create a Task
The handler calls iotmiCdmb_requestHeaderParser(), which extracts the six header fields and detaches the "p" (payload) object into a freshly allocated iotmiCdmb_task_t:
// include/iotshd_cdmb_common_types.h
typedef struct iotmiCdmb_task {
char* reqHeaderItems[IOTMI_CDMB_REQ_HEADER_ITEM_NUM];
cJSON* reqPayload;
cJSON* rspPayload;
} iotmiCdmb_task_t;
The task struct is the CDMB's unit of work — it carries the request in and the response out.
Step 3 — Route by Message Type
Before protocol routing, the handler checks the messageType header. Discovery requests (D2C_HUB_START_LOCAL_DISCOVERY) go to iotmiCdmb_handleDiscoveryTask(); everything else goes to iotmiCdmb_handleTask():
if (strncmp(msg_type, "D2C_HUB_START_LOCAL_DISCOVERY",
strlen("D2C_HUB_START_LOCAL_DISCOVERY")) == 0) {
status = iotmiCdmb_handleDiscoveryTask(task);
} else {
status = iotmiCdmb_handleTask(task);
}
Step 4 — Route to the Plugin
Inside iotmiCdmb_handleTask(), the protocol enum drives a switch statement. Each case is guarded by both a compile-time #ifdef and a runtime flag:
case PROTOCOL_ZWAVE:
if (zwave_support_runtime_enabled) {
status = IotmiCdmbZWave_TaskHandler(task);
}
break;
This two-layer guard means you can build the SDK with Z-Wave support but still disable it at launch via a command-line argument (disable-zwave).
Step 5 — Build and Return the Response
After the plugin populates task->rspPayload, the CDMB wraps it back into the standard envelope with iotmiCdmb_buildTaskResponse() and hands the buffer back to IPC.
Plugin Loading: Compile-Time Linking
Unlike a dynamic plugin system, the CDMB links protocol plugins as static libraries selected at build time. The CMakeLists.txt uses CMake flags to conditionally include each one:
# src/CMakeLists.txt (simplified)
if(${EN_ZWAVE_CONTROL})
target_link_libraries(${PROJECT_NAME} PUBLIC
iotmi_cdmb_zwave)
endif()
The three flags — EN_ZWAVE_CONTROL, EN_ZIGBEE_CONTROL, EN_MATTER_CONTROL — each pull in a corresponding static library (iotmi_cdmb_zwave, iotmi_cdmb_zigbee_plugin, iotmi_cdmb_matter). No dlopen, no plugin directories — just linker-level composition.
Service Lifecycle
The CDMB process starts in iotshd_cdmb_main.c and follows a clear five-step lifecycle:
- Daemonize (optional
-Bflag). - Configure IPC URLs — async, sync, and publish sockets.
- Register
iotmiCdmbSvc_startas the IPC feature callback. - Start IPC — this triggers
iotmiCdmbSvc_start(), which registers the command handler and initializes every enabled plugin. - Shutdown —
iotmiCdmbSvc_stop()tears down plugins and deregisters handlers.
The service succeeds only if at least one plugin initializes. If every plugin fails, the whole CDMB exits with an error — there is no point running a bridge with nothing on the other side.
Event Publishing
Plugins don't just respond to commands — they also push unsolicited events (e.g., a sensor reporting a temperature change). The CDMB provides iotmiCdmb_publishEvent() for this:
// include/iotshd_cdmb_svc.h
void iotmiCdmb_publishEvent(
IotmiIpc_EventId event_id,
const uint8_t *data, uint32_t data_len);
Each event gets a monotonically increasing sequence number (protected by a mutex) so the Agent can detect gaps. The event data is wrapped in an iotmiCdmb_eventData_t struct that pairs the sequence with the payload string, then published over the IPC pub socket as IOTMI_IPC_EVENT_CDMB_REPORT.
Custom Plugin Path
For protocols beyond Z-Wave/Zigbee/Matter, the CDMB supports a custom plugin channel. Instead of matching an eNId prefix, it looks for a customProtocolClientName field in the payload. If found, the task is serialized and forwarded over a dedicated IPC pub socket to the external plugin process.
Custom plugins connect to the CDMB via a socket assignment handshake (IOTMI_IPC_SOCKET_INDEX_REQUEST) and maintain liveness with heartbeats (IOTMI_IPC_SOCKET_HEARTBEAT_REQUEST). We will explore this in Chapter 8.
Key Takeaways
| Concept | Detail |
|---|---|
| Routing | 3-char eNId prefix → protocol enum → switch dispatch |
| Message format | 6 required header fields + "p" payload |
| Plugin binding | Static libraries, selected by CMake flags |
| Runtime control | Plugins can be disabled via CLI args at launch |
| Events | Sequence-numbered, published over IPC pub socket |
| Thread safety | Mutexes guard service init and event sequence |
Conclusion
The CDMB is the SDK's traffic controller — it never touches a radio or a wire, but every device command and event passes through it. Its design is deliberately thin: parse, route, respond. The real protocol work happens one layer deeper, inside the plugins.
In Chapter 8: Protocol Plugins, we will open up those plugin libraries and see how Z-Wave frames, Zigbee clusters, and custom protocols actually get translated into the common data model.