Implementing Custom BLE Mesh Models for Matter Lighting Control: A Step-by-Step Guide with Python Scripting and C Firmware

As the Bluetooth SIG continues to advance the Mesh Model specification—with the recent release of v1.1.1 in November 2025—the integration of BLE Mesh with Matter has become a critical topic for smart lighting developers. The Mesh Model specification (MMDL_v1.1.1) defines lighting control models, including the Light Lightness, Light CTL, and Light HSL models, which form the foundation for Matter lighting clusters. However, real-world deployments often require custom models that extend these base definitions to support vendor-specific features, such as advanced dimming curves, scene-based color transitions, or sensor fusion for occupancy-driven lighting. This article provides a step-by-step guide to implementing custom BLE Mesh models for Matter lighting control, combining Python scripting for rapid prototyping and C firmware for production deployment.

Understanding the BLE Mesh Model Architecture for Lighting

The BLE Mesh Model specification (v1.1.1) organizes lighting control into a hierarchy of models. At the core is the Generic OnOff Server, which provides basic on/off control. Above this, the Light Lightness Server model adds a lightness state (0–65535), while the Light CTL Server extends this with correlated color temperature (CCT). For Matter compatibility, these models must be mapped to the Matter Lighting Control Cluster, which uses a 0–100% range for level control and a Kelvin-based color temperature range (typically 1000–20000 K).

A custom model for Matter lighting control might, for example, add a "dynamic fade" feature that interpolates between two lightness states over a configurable duration. This is not part of the standard MMDL v1.1.1, but can be implemented as a vendor model with a unique SIG-assigned or vendor-specific model identifier. The model must define its own states (e.g., FadeDuration, FadeTarget) and messages (e.g., FadeSet, FadeStatus).

Step 1: Prototyping the Custom Model with Python Scripting

Before diving into C firmware, Python scripting allows rapid validation of the model's behavior. Using a BLE Mesh simulation framework (e.g., the open-source py-mesh library), we can define the custom model's state machine and message handling.

# python_custom_model_prototype.py
import asyncio
from py_mesh import MeshNode, Model, State, Message

class DynamicFadeModel(Model):
    MODEL_ID = 0x0001  # Vendor-specific, replace with your ID
    OPCODE_FADE_SET = 0xC1
    OPCODE_FADE_STATUS = 0xC2

    def __init__(self, node):
        super().__init__(node, self.MODEL_ID)
        self.state = {
            'current_lightness': 0,
            'target_lightness': 0,
            'fade_duration': 1000,  # milliseconds
            'fade_start_time': None
        }

    async def handle_message(self, src, opcode, params):
        if opcode == self.OPCODE_FADE_SET:
            target = int.from_bytes(params[0:2], 'little')
            duration = int.from_bytes(params[2:4], 'little')
            self.state['target_lightness'] = target
            self.state['fade_duration'] = duration
            self.state['fade_start_time'] = self.node.get_time()
            # Respond with FadeStatus
            status = params[0:2] + params[2:4]
            self.send_message(src, self.OPCODE_FADE_STATUS, status)
        return True

    async def update(self):
        if self.state['fade_start_time'] is not None:
            elapsed = self.node.get_time() - self.state['fade_start_time']
            if elapsed >= self.state['fade_duration']:
                self.state['current_lightness'] = self.state['target_lightness']
                self.state['fade_start_time'] = None
            else:
                ratio = elapsed / self.state['fade_duration']
                self.state['current_lightness'] = int(
                    self.state['current_lightness'] + 
                    (self.state['target_lightness'] - self.state['current_lightness']) * ratio
                )

# Usage in simulation
node = MeshNode()
model = DynamicFadeModel(node)
node.add_model(model)
asyncio.run(node.run())

This script defines a custom model with a linear fade between lightness values. The FadeSet message includes a 2-byte target lightness (0–65535) and a 2-byte duration (in ms). The update method, called periodically, interpolates the current lightness. In a simulation, you can test the model's response to multiple FadeSet messages and verify that the state transitions are smooth.

Step 2: Mapping to Matter Lighting Control

Matter uses a data model based on ZCL (Zigbee Cluster Library) clusters. For lighting, the LevelControl cluster maps to the BLE Mesh Light Lightness model, while the ColorControl cluster maps to Light CTL or Light HSL. A custom model must expose its states via a Matter-compatible bridge. In practice, this means implementing a translation layer that converts BLE Mesh messages to Matter attribute updates.

For the dynamic fade model, the Matter bridge would subscribe to the FadeStatus message and update the CurrentLevel attribute of the LevelControl cluster. The bridge must also handle the inverse: when Matter sends a MoveToLevel command with a transition time, the bridge should send a FadeSet message to the BLE Mesh node.

// matter_bridge_translation.c (pseudocode)
void matter_move_to_level_handler(uint8_t level, uint16_t transition_time_ms) {
    // Convert Matter level (0-100%) to BLE Mesh lightness (0-65535)
    uint16_t lightness = (level * 65535) / 100;
    // Convert transition time (in 100ms units per Matter spec) to ms
    uint16_t duration_ms = transition_time_ms * 100;
    
    // Build FadeSet message
    uint8_t payload[4];
    payload[0] = lightness & 0xFF;
    payload[1] = (lightness >> 8) & 0xFF;
    payload[2] = duration_ms & 0xFF;
    payload[3] = (duration_ms >> 8) & 0xFF;
    
    // Send to BLE Mesh node (using vendor model opcode)
    ble_mesh_send_vendor_message(NODE_ADDR, MODEL_ID, OPCODE_FADE_SET, payload, 4);
}

Step 3: Implementing the Custom Model in C Firmware

For production, the custom model must be implemented in C firmware using the Bluetooth Mesh SDK (e.g., Nordic nRF5 SDK or Zephyr). The following example shows a minimal implementation for Zephyr RTOS, which supports the MMDL v1.1.1 specification.

// custom_lighting_model.c
#include <bluetooth/mesh.h>

#define VENDOR_MODEL_ID 0x0001
#define OP_FADE_SET 0xC1
#define OP_FADE_STATUS 0xC2

struct custom_lighting_state {
    uint16_t current_lightness;
    uint16_t target_lightness;
    uint32_t fade_duration_ms;
    uint32_t fade_start_time;
};

static struct custom_lighting_state state;

static void fade_set_handler(struct bt_mesh_model *model,
                             struct bt_mesh_msg_ctx *ctx,
                             struct net_buf_simple *buf) {
    state.target_lightness = net_buf_simple_pull_le16(buf);
    state.fade_duration_ms = net_buf_simple_pull_le16(buf);
    state.fade_start_time = k_uptime_get();
    
    // Send FadeStatus response
    struct net_buf_simple *msg = NET_BUF_SIMPLE(4);
    net_buf_simple_add_le16(msg, state.target_lightness);
    net_buf_simple_add_le16(msg, state.fade_duration_ms);
    bt_mesh_model_send(model, ctx, msg, NULL, NULL);
}

static void fade_update(struct bt_mesh_model *model) {
    if (state.fade_start_time == 0) return;
    
    uint32_t elapsed = k_uptime_get() - state.fade_start_time;
    if (elapsed >= state.fade_duration_ms) {
        state.current_lightness = state.target_lightness;
        state.fade_start_time = 0;
    } else {
        // Linear interpolation (use integer math to avoid floating point)
        uint32_t diff = state.target_lightness - state.current_lightness;
        state.current_lightness += (diff * elapsed) / state.fade_duration_ms;
    }
    
    // Update actual PWM or LED driver
    pwm_set_lightness(state.current_lightness);
}

// Model operation structure
static const struct bt_mesh_model_op custom_ops[] = {
    { OP_FADE_SET, 4, fade_set_handler },
    BT_MESH_MODEL_OP_END,
};

// Model instance
struct bt_mesh_model custom_model = BT_MESH_MODEL_VND(
    BT_COMP_ID_VENDOR, VENDOR_MODEL_ID,
    custom_ops, NULL, NULL
);

// In main application, call fade_update periodically (e.g., in a timer)
void custom_model_timer_handler(struct k_timer *dummy) {
    fade_update(&custom_model);
}

K_TIMER_DEFINE(custom_model_timer, custom_model_timer_handler, NULL);

Performance Analysis and Protocol Considerations

When implementing custom models, performance is critical. The BLE Mesh protocol operates on a managed flooding basis, with message delivery times typically between 10–100 ms per hop for a reliable network. For lighting control, the custom model's state update frequency must not exceed the network's capacity. The dynamic fade model above updates at the application layer rate (e.g., 10 Hz), which is well within the limits of a BLE Mesh network with 50–100 nodes.

However, there are two key protocol considerations:

  • Message Segmentation: The FadeSet message is only 4 bytes, fitting within a single BLE Mesh transport PDU (up to 11 bytes of payload). If the custom model requires larger payloads (e.g., for color temperature curves), you must implement segmentation and reassembly at the model layer, or use the BLE Mesh foundation model's segmentation support.
  • State Binding: The MMDL v1.1.1 specification defines state binding between models (e.g., Light Lightness and Generic OnOff). For a custom model, you must explicitly implement binding. For example, when the dynamic fade model reaches its target, it should also update the Generic OnOff state if the target is zero (off) or non-zero (on).

Testing and Validation

To validate the custom model, use a BLE Mesh test harness that includes:

  • A Python script acting as a virtual node that sends FadeSet messages at random intervals.
  • A C firmware node that logs state transitions via UART.
  • A Matter bridge that monitors the current lightness attribute.

Example test script:

# test_custom_model.py
import time
from py_mesh import MeshNetwork

net = MeshNetwork()
net.connect('COM3')  # Serial connection to C firmware node

# Send multiple fade commands
for target in [10000, 30000, 50000]:
    net.send_vendor_message(0x0001, 0xC1, 
        target.to_bytes(2, 'little') + (2000).to_bytes(2, 'little'))
    time.sleep(2.5)  # Wait for fade completion

# Verify final state
status = net.request_status(0x0001, 0xC2)
print(f"Final lightness: {int.from_bytes(status[0:2], 'little')}")

Conclusion

Implementing custom BLE Mesh models for Matter lighting control requires a careful balance between specification compliance and flexibility. By prototyping with Python scripting, you can iterate rapidly on the model's behavior, while the C firmware implementation ensures real-time performance and low power consumption. The key is to maintain compatibility with the MMDL v1.1.1 state machine while extending it for vendor-specific features. As the Bluetooth SIG continues to update the Mesh Model specification (with v1.1.1 now adopted), developers should monitor for new standard models that may reduce the need for custom implementations. For now, the approach outlined here provides a robust foundation for building advanced lighting control systems that bridge BLE Mesh and Matter ecosystems.

常见问题解答

问: What is the purpose of implementing custom BLE Mesh models for Matter lighting control, and when is it necessary?

答: Custom BLE Mesh models extend the standard lighting models defined in the Mesh Model specification (MMDL v1.1.1), such as Light Lightness, Light CTL, and Light HSL, to support vendor-specific features not covered by the base definitions. This is necessary for real-world deployments requiring advanced dimming curves, scene-based color transitions, sensor fusion for occupancy-driven lighting, or other unique functionalities. Custom models allow developers to map these features to Matter Lighting Control Clusters while maintaining compatibility with the BLE Mesh ecosystem.

问: How does Python scripting help in the development of custom BLE Mesh models for lighting?

答: Python scripting enables rapid prototyping and validation of a custom model's behavior before committing to C firmware development. Using a BLE Mesh simulation framework like the open-source py-mesh library, developers can define the model's state machine, message handling, and state transitions (e.g., FadeDuration, FadeTarget) in a high-level language. This approach reduces iteration time, allows testing of edge cases, and ensures the model logic is correct before porting to resource-constrained embedded systems.

问: What are the key components required to define a custom BLE Mesh model for lighting control?

答: A custom BLE Mesh model must define a unique model identifier (SIG-assigned or vendor-specific), its own states (e.g., FadeDuration, FadeTarget), and associated messages (e.g., FadeSet, FadeStatus). For Matter compatibility, the model's states must be mapped to the Matter Lighting Control Cluster's range (0–100% for level, 1000–20000 K for color temperature). The model should also implement a state machine to handle transitions, such as interpolating between lightness states over a configurable duration for a dynamic fade feature.

问: How does the BLE Mesh Model specification v1.1.1 relate to Matter lighting clusters?

答: The BLE Mesh Model specification v1.1.1 defines lighting control models like Generic OnOff Server, Light Lightness Server, and Light CTL Server, which form the foundation for Matter lighting clusters. For Matter compatibility, these models must be mapped to the Matter Lighting Control Cluster, which uses a 0–100% range for level control and a Kelvin-based color temperature range (typically 1000–20000 K). Custom models extend this mapping by adding vendor-specific features while ensuring interoperability with the Matter standard.

问: What are the challenges of transitioning from a Python prototype to C firmware for custom BLE Mesh models?

答: Transitioning from Python to C firmware involves addressing resource constraints (e.g., limited memory, processing power) and real-time requirements in embedded systems. The Python prototype's state machine and message handling must be re-implemented in C with efficient data structures and interrupt-driven or RTOS-based scheduling. Additionally, low-level BLE Mesh stack integration, memory management, and debugging on hardware (e.g., using logic analyzers or BLE sniffer tools) are critical steps. The Python prototype serves as a behavioral reference, but the C firmware requires careful optimization for production deployment.

💬 欢迎到论坛参与讨论: 点击这里分享您的见解或提问

Login

Bluetoothchina Wechat Official Accounts

qrcode for gh 84b6e62cdd92 258