Chapter 8: Protocol CDMB Plugins
In Chapter 7, we explored CDMBCore — the engine that receives tasks from the cloud and dispatches them. But CDMBCore doesn't speak Zigbee or Z-Wave itself. It delegates to protocol plugins — the translators that convert abstract cluster operations into real radio commands a device understands.
Think of it like a multilingual call center. CDMBCore is the dispatcher who takes the customer request. The Zigbee plugin is the agent who speaks Zigbee, and the Z-Wave plugin is the agent who speaks Z-Wave. Same request format in, different protocol-specific commands out.
This chapter walks through both plugins, how they register with CDMB, and how a request flows from an abstract cluster operation all the way to a protocol-level command.
8.1 Where the Plugins Live
Both plugins are static libraries that get linked into CDMBCore at build time:
control/plugins/
├── IoTSmartHomeDevice-Zigbee-CDMB-Plugin/
│ ├── src/
│ │ ├── iotmi_cdmb_zigbee_core.c ← Entry point & cluster map
│ │ ├── iotmi_cdmb_zigbee_operations.c ← ZCL read/write handlers
│ │ ├── iotmi_cdmb_zigbee_events.c ← Event/report handling
│ │ └── clusters/ ← Per-cluster handlers
│ └── include/
└── IoTSmartHomeDevice-Zwave-CDMB-Plugin/
├── src/
│ ├── iotshd_cdmb_zwave_svc.c ← Entry point & task handler
│ ├── iotshd_cdmb_zwave_data.c ← CC-to-cluster mapping
│ └── command_classes/ ← Per-CC handlers
└── include/
Each plugin exposes a task handler function and an init function that CDMBCore calls. The pattern is the same for both: initialize → register cluster mappings → handle tasks.
8.2 The Plugin Interface
Both plugins implement the same contract with CDMBCore. Each provides:
- An init function to set up protocol-specific resources
- A task handler that CDMBCore calls with an
iotmiCdmb_task_t - A cluster mapping table that tells CDMB which clusters this plugin supports
For the Zigbee plugin, these are declared in iotmi_cdmb_zigbee_core.h:
// Zigbee-CDMB-Plugin/include/iotmi_cdmb_zigbee_core.h
iotmi_statusCode_t iotmiCdmb_zigbeeInit();
iotmi_statusCode_t iotmiCdmb_zigbeeTaskHandler(
iotmiCdmb_task_t* task);
For the Z-Wave plugin, the equivalent lives in iotshd_cdmb_zwave_svc.h:
// Zwave-CDMB-Plugin/include/iotshd_cdmb_zwave_svc.h
iotmi_statusCode_t IotmiCdmbZWave_TaskHandler(
iotmiCdmb_task_t* task);
When CDMBCore receives a task, it checks the protocol field and routes to the right handler. The task struct carries everything: node ID, endpoint, cluster, command, and a JSON payload.
8.3 The Zigbee Plugin: Cluster Handlers
The Zigbee plugin's core design revolves around a cluster map table — a static array that maps Matter cluster names to Zigbee ZCL cluster IDs and their handler functions.
Here's the actual table from iotmi_cdmb_zigbee_core.c:
// Zigbee-CDMB-Plugin/src/iotmi_cdmb_zigbee_core.c
clusterMapTableEntry_t clusterMapTable[] = {
{ "aws.OnOff", 0x0006, ... },
{ "aws.LevelControl",0x0008, ... },
{ "aws.DoorLock", 0x0101, ... },
{ "aws.Thermostat", 0x0201, ... },
{ "aws.ColorControl",0x0300, ... },
// ... 13 entries total
};
Each entry links a Matter cluster name (like "aws.Thermostat") to a Zigbee ZCL cluster ID (like 0x0201) and a pointer to a zigbeeClusterInfo_t struct that describes the cluster's attributes and commands.
Cluster Info Structs
Each supported cluster has a dedicated file under src/clusters/. For example, the thermostat cluster is defined in iotmi_cdmb_zigbee_thermostat.c:
// Zigbee-CDMB-Plugin/src/clusters/iotmi_cdmb_zigbee_thermostat.c
zigbeeClusterInfo_t zigbeethermostatInfo = {
.zigbeeClusterId = IOTMI_ZIGBEE_ZCL_THERMOSTAT_CLUSTER_ID,
.attributeCount = 4,
.attributes[0].matterId = 0x0,
.attributes[0].zigbeeId = ..._LOCAL_TEMPERATURE_...,
.attributes[0].type = IOTMI_ZIGBEE_ZCL_INT16S_ATTRIBUTE_TYPE,
// ... more attributes and commands
};
This struct is the Rosetta Stone between Matter and Zigbee. When CDMBCore says "read attribute 0x0 from the Thermostat cluster," the plugin looks up the corresponding Zigbee attribute ID and type, then builds a ZCL Read Attributes frame.
Supported Zigbee Clusters
| Cluster File | Matter Cluster | ZCL Cluster ID |
|---|---|---|
iotmi_cdmb_zigbee_binary_switch.c |
aws.OnOff | 0x0006 |
iotmi_cdmb_zigbee_level_control.c |
aws.LevelControl | 0x0008 |
iotmi_cdmb_zigbee_door_lock.c |
aws.DoorLock | 0x0101 |
iotmi_cdmb_zigbee_thermostat.c |
aws.Thermostat | 0x0201 |
iotmi_cdmb_zigbee_color_control.c |
aws.ColorControl | 0x0300 |
iotmi_cdmb_zigbee_sensors.c |
aws.OccupancySensing | 0x0406 |
iotmi_cdmb_zigbee_basic_information.c |
aws.BasicInformationCluster | 0x0028 |
8.4 The Z-Wave Plugin: Command Class Handlers
The Z-Wave plugin follows a similar pattern, but instead of ZCL clusters, it maps to Z-Wave Command Classes (CCs). The mapping lives in iotshd_cdmb_zwave_data.c using ClusterCommandClassMapItem structs.
Here's the Binary Switch mapping as an example:
// Zwave-CDMB-Plugin/src/iotshd_cdmb_zwave_data.c
ClusterCommandClassMapItem kOnOffSwitchBinaryMap = {
.cluster_id = CLUSTER_ON_OFF,
.cc_id = COMMAND_CLASS_SWITCH_BINARY,
.command_info = { ... },
.attribute_info = { .attribute_count = 1 }
};
Each ClusterCommandClassMapItem connects a Matter cluster ID to a Z-Wave CC ID, along with the command and attribute mappings between the two worlds.
Command Class Handler Files
The actual protocol commands are implemented in src/command_classes/:
| CC Handler File | Z-Wave Command Class | Matter Cluster |
|---|---|---|
iotmi_cdmb_zwave_switch_binary.c |
SWITCH_BINARY | OnOff |
iotmi_cdmb_zwave_switch_multilevel.c |
SWITCH_MULTILEVEL | LevelControl |
iotmi_cdmb_zwave_door_lock.c |
DOOR_LOCK | DoorLock |
iotmi_cdmb_zwave_battery.c |
BATTERY | PowerSource |
iotmi_cdmb_zwave_meter.c |
METER | ElectricalMeasurement |
iotmi_cdmb_zwave_notification.c |
NOTIFICATION | BooleanState/SmokeCOAlarm |
iotmi_cdmb_zwave_sensor_multilevel.c |
SENSOR_MULTILEVEL | TemperatureMeasurement |
Z-Wave Custom Cluster Schemas
One unique feature of the Z-Wave plugin is custom cluster schemas for Z-Wave-specific functionality that doesn't map cleanly to standard Matter clusters. These are JSON schema files in src/cluster_schemas/:
// Zwave-CDMB-Plugin/src/iotshd_cdmb_zwave_svc.c
static const char *IotmiCdmbZWave_GetCustomizedClusterSchema(
uint16_t cluster_id) {
switch (cluster_id) {
case ZWAVE_DOORLOCK_CLUSTER_ID:
return ZWAVE_DOORLOCK_SCHEMA_JSON;
case ZWAVE_USERCODE_CLUSTER_ID:
return ZWAVE_USERCODE_SCHEMA_JSON;
// ...
}
}
This allows the Z-Wave plugin to expose rich Z-Wave features (like door lock logging or user codes) that go beyond what standard Matter clusters define.
8.5 How a Task Flows Through a Plugin
Let's trace a concrete example: reading the current temperature from a Zigbee thermostat. Here's the sequence from CDMBCore through the Zigbee plugin to the radio:
sequenceDiagram
participant CDMB as CDMBCore
participant Core as zigbee_core
participant Ops as zigbee_operations
participant ZCL as Zigbee Stack
CDMB->>Core: iotmiCdmb_zigbeeTaskHandler(task)
Core->>Core: Look up "aws.Thermostat" in clusterMapTable
Core->>Ops: zigbeeReadPayloadHandler(params, thermostatInfo)
Ops->>ZCL: Send ZCL Read Attributes (cluster 0x0201, attr 0x0000)
ZCL-->>Core: ZCL Response → build JSON response
Here's what happens at each step:
- CDMBCore calls
iotmiCdmb_zigbeeTaskHandler()with the parsed task - zigbee_core extracts the cluster name from the JSON payload and looks it up in
clusterMapTableto find thezigbeeClusterInfo_t - For a read command (
0xff02), it callszigbeeReadPayloadHandler()in zigbee_operations - The operations module translates Matter attribute IDs to Zigbee attribute IDs using the cluster info struct, then builds a ZCL Read Attributes frame
- The Zigbee stack sends the frame over the radio and returns the response, which gets translated back to Matter format
The Z-Wave plugin follows the same pattern but with its own types. IotmiCdmbZWave_TaskHandler() parses the request, creates a ZWaveTask, initializes synchronization primitives (semaphores for the async Z-Wave radio), and calls IotmiCdmbZWave_ExecuteTask() which dispatches to the appropriate CC handler.
8.6 Deep Dive: BasicInformation Cluster (Zigbee)
The BasicInformation cluster is a great example of a custom read handler. Unlike most clusters that use the generic read path, BasicInformation has its own handler because it pulls data from the Zigbee network layer rather than sending ZCL commands.
Notice how it's registered in the cluster map table with an explicit read handler:
// Zigbee-CDMB-Plugin/src/iotmi_cdmb_zigbee_core.c
{ "aws.BasicInformationCluster", 0x0028,
NULL, // no generic write handler
zigbeeBasicInformationClusterReadHandler, // custom!
&zigbeeBasicInformationClusterInfo },
The handler in iotmi_cdmb_zigbee_basic_information.c reads device metadata directly from the Zigbee network manager:
// Zigbee-CDMB-Plugin/src/clusters/iotmi_cdmb_zigbee_basic_information.c
iotmi_zigbee_status_t res =
iotmiZigbee_networkGetNodeFromNodeId(
task->nodeId, &nodeInfo);
// Then maps nodeInfo fields to Matter attributes:
// MATTER_VENDOR_ID ← nodeInfo.manufacturer_code
// MATTER_PRODUCT_ID ← nodeInfo.device_info...device_id
This pattern — a custom handler for clusters that need special logic — is how the plugin stays flexible. Most clusters use the generic ZCL read/write path, but any cluster can override with its own handler when needed.
8.7 Zigbee vs. Z-Wave: Side-by-Side
Both plugins solve the same problem (protocol translation) but reflect the differences in their underlying protocols:
| Aspect | Zigbee Plugin | Z-Wave Plugin |
|---|---|---|
| Mapping unit | ZCL Cluster → Matter Cluster | Z-Wave CC → Matter Cluster |
| Mapping table | clusterMapTable[] (static array) |
ClusterCommandClassMapItem structs |
| Handler files | src/clusters/*.c |
src/command_classes/*.c |
| Communication | Synchronous ZCL frames | Async with semaphores |
| Custom schemas | Not needed (ZCL maps well to Matter) | JSON schemas for Z-Wave-specific features |
| Init function | iotmiCdmb_zigbeeInit() |
Initialized via CDMBCore |
| Task handler | iotmiCdmb_zigbeeTaskHandler() |
IotmiCdmbZWave_TaskHandler() |
The Z-Wave plugin is notably more complex because Z-Wave commands are asynchronous — the plugin must create semaphores and wait for callbacks, while Zigbee ZCL operations are more straightforward.
8.8 How Plugins Register with CDMB
Both plugins register with CDMBCore through their init functions and the static linking of their task handlers. The flow is:
- At startup, CDMBCore calls each plugin's init function (e.g.,
iotmiCdmb_zigbeeInit()) - The plugin sets up its protocol stack (Zigbee network manager, Z-Wave manager)
- When a task arrives, CDMBCore checks the protocol field in the request payload
- It routes to the appropriate task handler based on protocol type
The protocol type is defined in iotshd_cdmb.h:
// iotshd_cdmb.h
typedef enum {
CDMB_PROTOCOL_ZWAVE = 1,
CDMB_PROTOCOL_ZIGBEE,
CDMB_PROTOCOL_MATTER
} cdmb_protocol_type_t;
CDMBCore maintains separate task lists for each protocol (tasks_zw and tasks_zb in the cdmb_ctx_t struct), ensuring that Zigbee and Z-Wave operations don't interfere with each other.
8.9 Adding a New Cluster (Conceptual)
If you needed to add support for a new device type, here's the pattern for each plugin:
Zigbee:
1. Create a new file in src/clusters/ (e.g., iotmi_cdmb_zigbee_my_cluster.c)
2. Define a zigbeeClusterInfo_t with attribute and command mappings
3. Add an entry to clusterMapTable[] in iotmi_cdmb_zigbee_core.c
Z-Wave:
1. Create a new file in src/command_classes/ (e.g., iotmi_cdmb_zwave_my_cc.c)
2. Define a ClusterCommandClassMapItem in iotshd_cdmb_zwave_data.c
3. Implement the send-command function following the pattern in existing CC handlers
The plugin architecture makes this straightforward — you only need to define the mapping and implement the protocol-specific logic. CDMBCore handles everything else.
8.10 Key Takeaways
- CDMB plugins are protocol translators — they convert abstract Matter cluster operations into Zigbee ZCL or Z-Wave CC commands
- Both plugins use mapping tables — the Zigbee plugin maps cluster names to ZCL IDs via
clusterMapTable[], while the Z-Wave plugin usesClusterCommandClassMapItemstructs - Handler files are organized by cluster/CC — each supported device type has its own source file under
clusters/orcommand_classes/ - Custom handlers override the generic path — BasicInformation is a good example of a cluster that needs special logic
- Z-Wave adds async complexity — semaphores and callbacks handle the asynchronous nature of Z-Wave radio communication
- Plugins are static libraries — they're linked into CDMBCore at build time, not loaded dynamically
Conclusion
The CDMB plugins are where the rubber meets the road — where abstract cloud commands become real radio signals that turn on lights, lock doors, and read thermostats. The Zigbee plugin translates to ZCL frames, the Z-Wave plugin translates to CC commands, and both follow the same architectural pattern of mapping tables and per-cluster handlers.
Together with CDMBCore from Chapter 7 and the middleware services from Chapter 4, the plugins complete the control plane picture: cloud → CDMBCore → plugin → protocol stack → device.
In Chapter 9, we'll shift to the Device Agent — the component that manages device lifecycle, handles provisioning, and coordinates between the cloud and the local control plane we've been exploring.
Next: Chapter 9 — Device Agent