Bluetooth Mesh Light CTL (color tuned light) sample

Is there a sample app that uses the Zephyr Light CTL Bluetooth Mesh model?  I've tried to add that model to the light_ctrl sample app, but get a crash during configuration so I must be doing something wrong. 

The onoff_level_lighting_vnd app claims to support that model but it doesn't seem to be using the Zephyr lighting models. 

Thanks

Parents
  • Hi Jack, 
    I think you are right. There isn't a sample for that model right now. Please let us know the issue you are seeing we can try to take a look. 
    We have limited capacity in the holidays period but will be back to full staff by beginning of the year. 

  • Thanks, here goes.

    I started with the VSCode plugin light_ctrl sample using version 2.8.0 of the toolchain and libraries. Build machines with Windows and Linux tried.  Target is nRF52840DK. 

    The unmodified sample builds, runs, and provisions and functions correctly with the nRF Mesh Android app. 

    After the modifications below, the code compiles and runs, but during initialization I get an assert showing on the console (see below), and an "Initial Configuration Failed" message from the app.  

    Note that the assert states that temp srv wasn't properly initialized.  On debugging, however, it appeared that the tmp srv model was properly initialized and the light CTL model was not, however I have a low degree of confidence in this assessment. 

    model_handler.c was modified from the sample as follows:

    Add headers

    #include <bluetooth/mesh/light_ctrl_srv.h>
    #include <bluetooth/mesh/light_ctl_srv.h>

    Add placeholder handlers

    static void temp_srv_set(struct bt_mesh_light_temp_srv *srv,
                             struct bt_mesh_msg_ctx *ctx,
                             const struct bt_mesh_light_temp_set *set,
                             struct bt_mesh_light_temp_status *status)
    {
        // Implement setting the temperature on your hardware
    }

    static void temp_srv_get(struct bt_mesh_light_temp_srv *srv,
                             struct bt_mesh_msg_ctx *ctx,
                             struct bt_mesh_light_temp_status *status)
    {
        // Implement reading the current temperature state from your hardware
    }

    static const struct bt_mesh_light_temp_srv_handlers temp_srv_handlers = {
        .set = temp_srv_set,
        .get = temp_srv_get,
    };

    static struct bt_mesh_light_temp_srv light_temp_srv =
        BT_MESH_LIGHT_TEMP_SRV_INIT(&temp_srv_handlers);

    static struct bt_mesh_light_ctl_srv light_ctl_srv =

        BT_MESH_LIGHT_CTL_SRV_INIT(&lightness_srv_handlers, &temp_srv_handlers);

    Add light ctl and light temp srv models:

    static struct bt_mesh_elem elements[] = {
        BT_MESH_ELEM(1,
                     BT_MESH_MODEL_LIST(
                         BT_MESH_MODEL_CFG_SRV,
                         BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
                         BT_MESH_MODEL_LIGHTNESS_SRV(&my_ctx.lightness_srv),
                         BT_MESH_MODEL_SCENE_SRV(&scene_srv),
                         BT_MESH_MODEL_SENSOR_SRV(&sensor_srv),
                         BT_MESH_MODEL_LIGHT_CTL_SRV(&light_ctl_srv)
                                        ),
                     BT_MESH_MODEL_NONE),
        BT_MESH_ELEM(2,
                     BT_MESH_MODEL_LIST(
                         BT_MESH_MODEL_LIGHT_TEMP_SRV(&light_ctl_srv.temp_srv)
                     ),
                     BT_MESH_MODEL_NONE),
        BT_MESH_ELEM(3,
                     BT_MESH_MODEL_LIST(
                         BT_MESH_MODEL_LIGHT_CTRL_SRV(&light_ctrl_srv)
                                       ),
                    BT_MESH_MODEL_NONE)
    };

    prj.conf changes


    CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE=33

    CONFIG_BT_MESH_LIGHT_CTL_SRV=y

    CONFIG_CORTEX_M_DEBUG_MONITOR_HOOK=y

    CONFIG_SEGGER_DEBUGMON=y

    Console output:

    *** Booting Mesh Light Fixture v2.8.0-c57f55d0686e ***
    *** Using nRF Connect SDK v2.8.0-a2386bfc8401 ***
    *** Using Zephyr OS v3.7.99-0bc3393fb112 ***
    Initializing...
    [00:00:00.008,941] <inf> fs_nvs: 8 Sectors of 4096 bytes
    [00:00:00.008,972] <inf> fs_nvs: alloc wra: 0, fb0
    [00:00:00.008,972] <inf> fs_nvs: data wra: 0, 44
    [00:00:00.009,124] <inf> bt_sdc_hci_driver: SoftDevice Controller build revision:
    fe 2c f9 6a 7f 36 22 2e a0 79 c0 40 be 2c 03 20 |.,.j.6". .y.@.,.
    40 c2 f3 32 |@..2
    [00:00:00.013,305] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
    [00:00:00.013,336] <inf> bt_hci_core: HW Variant: nRF52x (0x0002)
    [00:00:00.013,366] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 254.63788 Build 573996906
    [00:00:00.013,763] <inf> bt_hci_core: No ID address. App must call settings_load()
    Bluetooth initialized
    [00:00:00.439,453] <inf> bt_hci_core: Identity: EC:2C:E8:3F:5C:0E (random)
    [00:00:00.439,514] <inf> bt_hci_core: HCI: version 6.0 (0x0e) revision 0x104e, manufacturer 0x0059
    [00:00:00.439,544] <inf> bt_hci_core: LMP: version 6.0 (0x0e) subver 0x104e
    [00:00:00.442,474] <inf> bt_mesh_provisionee: Device UUID: 5c0b7b01-159d-4da6-a3f4-84feea62c259
    Mesh initialized
    Successfully enabled LC server
    [00:02:35.433,380] <inf> bt_mesh_main: Primary Element: 0x0020
    New light transition-> Lvl: 0, Time: 0, Delay: 0
    [00:02:35.443,359] <err> bt_mesh_light_ctl_srv: Light CTL srv[32]: Temp. srv not properly initialized
    ASSERTION FAIL @ WEST_TOPDIR/zephyr/kernel/work.c:687
    [00:02:35.443,878] <err> os: r0/a1: 0x00000004 r1/a2: 0x000002af r2/a3: 0x00000003
    [00:02:35.443,908] <err> os: r3/a4: 0x00000004 r12/ip: 0x0001b4a4 r14/lr: 0x0004443b
    [00:02:35.443,908] <err> os: xpsr: 0x01000000
    [00:02:35.443,939] <err> os: s[ 0]: 0x00000000 s[ 1]: 0x0004c9d3 s[ 2]: 0x00000000 s[ 3]: 0x00000000
    [00:02:35.443,969] <err> os: s[ 4]: 0x0005e348 s[ 5]: 0x2000dd34 s[ 6]: 0x00000000 s[ 7]: 0x0004bc3d
    [00:02:35.443,969] <err> os: s[ 8]: 0x20006ea8 s[ 9]: 0x0004bc7f s[10]: 0xffffffff s[11]: 0x2000dd34
    [00:02:35.444,030] <err> os: s[12]: 0x00000000 s[13]: 0x00044431 s[14]: 0x0005e348 s[15]: 0x00066cdc
    [00:02:35.444,030] <err> os: fpscr: 0x000002af
    [00:02:35.444,030] <err> os: Faulting instruction address (r15/pc): 0x0004bc6a
    [00:02:35.444,091] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic on CPU 0
    [00:02:35.444,122] <err> os: Current thread: 0x20006ea8 (sysworkq)
    [00:00:00.000,427] <err> qspi_nor: JEDEC id [ff ff ff] expect [c2 28 17]

  • Adding the files modified from the sample.

    /*
     * Copyright (c) 2020 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <bluetooth/mesh/models.h>
    #include <bluetooth/mesh/light_ctrl_srv.h>
    #include <bluetooth/mesh/light_ctl_srv.h>
    #include <dk_buttons_and_leds.h>
    #include "model_handler.h"
    #include "lc_pwm_led.h"
    
    
    #define PWM_SIZE_STEP 512
    
    struct lightness_ctx {
    	struct bt_mesh_lightness_srv lightness_srv;
    	struct k_work_delayable per_work;
    	uint16_t target_lvl;
    	uint16_t current_lvl;
    	uint32_t time_per;
    	uint32_t rem_time;
    };
    
    #if IS_ENABLED(CONFIG_BT_MESH_NLC_PERF_CONF)
    static const uint8_t cmp2_elem_offset1[2] = { 0, 1 };
    static const uint8_t cmp2_elem_offset2[1] = { 0 };
    
    static const struct bt_mesh_comp2_record comp_rec[2] = {
    	{
    	.id = BT_MESH_NLC_PROFILE_ID_BASIC_LIGHTNESS_CONTROLLER,
    	.version.x = 1,
    	.version.y = 0,
    	.version.z = 0,
    	.elem_offset_cnt = 2,
    	.elem_offset = cmp2_elem_offset1,
    	.data_len = 0
    	},
    	{
    	.id = BT_MESH_NLC_PROFILE_ID_ENERGY_MONITOR, /* Energy Monitor NLC Profile 1.0 */
    	.version.x = 1,
    	.version.y = 0,
    	.version.z = 0,
    	.elem_offset_cnt = 1,
    	.elem_offset = cmp2_elem_offset2,
    	.data_len = 0
    	}
    };
    
    static const struct bt_mesh_comp2 comp_p2 = {
    	.record_cnt = 2,
    	.record = comp_rec
    };
    #endif
    
    /* Set up a repeating delayed work to blink the DK's LEDs when attention is
     * requested.
     */
    static struct k_work_delayable attention_blink_work;
    static bool attention;
    
    static void attention_blink(struct k_work *work)
    {
    	static int idx;
    	const uint8_t pattern[] = {
    #if DT_NODE_EXISTS(DT_ALIAS(led0))
    		BIT(0),
    #endif
    #if DT_NODE_EXISTS(DT_ALIAS(led1))
    		BIT(1),
    #endif
    #if DT_NODE_EXISTS(DT_ALIAS(led2))
    		BIT(2),
    #endif
    #if DT_NODE_EXISTS(DT_ALIAS(led3))
    		BIT(3),
    #endif
    	};
    
    	if (attention) {
    		dk_set_leds(pattern[idx++ % ARRAY_SIZE(pattern)]);
    		k_work_reschedule(&attention_blink_work, K_MSEC(30));
    	} else {
    		dk_set_leds(DK_NO_LEDS_MSK);
    	}
    }
    
    static void attention_on(const struct bt_mesh_model *mod)
    {
    	attention = true;
    	k_work_reschedule(&attention_blink_work, K_NO_WAIT);
    }
    
    static void attention_off(const struct bt_mesh_model *mod)
    {
    	/* Will stop rescheduling blink timer */
    	attention = false;
    }
    
    static const struct bt_mesh_health_srv_cb health_srv_cb = {
    	.attn_on = attention_on,
    	.attn_off = attention_off,
    };
    
    static struct bt_mesh_health_srv health_srv = {
    	.cb = &health_srv_cb,
    };
    
    BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);
    
    static void start_new_light_trans(const struct bt_mesh_lightness_set *set,
    				  struct lightness_ctx *ctx)
    {
    	uint32_t step_cnt = abs(set->lvl - ctx->current_lvl) / PWM_SIZE_STEP;
    	uint32_t time = set->transition ? set->transition->time : 0;
    	uint32_t delay = set->transition ? set->transition->delay : 0;
    
    	ctx->target_lvl = set->lvl;
    	ctx->time_per = (step_cnt ? time / step_cnt : 0);
    	ctx->rem_time = time;
    	k_work_reschedule(&ctx->per_work, K_MSEC(delay));
    
    	printk("New light transition-> Lvl: %d, Time: %d, Delay: %d\n",
    	       set->lvl, time, delay);
    }
    
    static void periodic_led_work(struct k_work *work)
    {
    	uint16_t clamped_lvl;
    	struct lightness_ctx *l_ctx =
    		CONTAINER_OF(work, struct lightness_ctx, per_work.work);
    	l_ctx->rem_time -= l_ctx->time_per;
    
    	if ((l_ctx->rem_time <= l_ctx->time_per) ||
    	    (abs(l_ctx->target_lvl - l_ctx->current_lvl) <= PWM_SIZE_STEP)) {
    		struct bt_mesh_lightness_status status = {
    			.current = l_ctx->target_lvl,
    			.target = l_ctx->target_lvl,
    		};
    
    		l_ctx->current_lvl = l_ctx->target_lvl;
    		l_ctx->rem_time = 0;
    
    		bt_mesh_lightness_srv_pub(&l_ctx->lightness_srv, NULL, &status);
    
    		goto apply_and_print;
    	} else if (l_ctx->target_lvl > l_ctx->current_lvl) {
    		l_ctx->current_lvl += PWM_SIZE_STEP;
    	} else {
    		l_ctx->current_lvl -= PWM_SIZE_STEP;
    	}
    
    	k_work_reschedule(&l_ctx->per_work, K_MSEC(l_ctx->time_per));
    apply_and_print:
    	clamped_lvl = bt_mesh_lightness_clamp(&l_ctx->lightness_srv, l_ctx->current_lvl);
    	lc_pwm_led_set(clamped_lvl);
    	printk("Current light lvl: %u/65535\n", clamped_lvl);
    }
    
    static void light_set(struct bt_mesh_lightness_srv *srv,
    		      struct bt_mesh_msg_ctx *ctx,
    		      const struct bt_mesh_lightness_set *set,
    		      struct bt_mesh_lightness_status *rsp)
    {
    	struct lightness_ctx *l_ctx =
    		CONTAINER_OF(srv, struct lightness_ctx, lightness_srv);
    
    	start_new_light_trans(set, l_ctx);
    	rsp->current = l_ctx->rem_time ? l_ctx->current_lvl : l_ctx->target_lvl;
    	rsp->target = l_ctx->target_lvl;
    	rsp->remaining_time = set->transition ? set->transition->time : 0;
    }
    
    static void light_get(struct bt_mesh_lightness_srv *srv,
    		      struct bt_mesh_msg_ctx *ctx,
    		      struct bt_mesh_lightness_status *rsp)
    {
    	struct lightness_ctx *l_ctx =
    		CONTAINER_OF(srv, struct lightness_ctx, lightness_srv);
    
    	rsp->current = bt_mesh_lightness_clamp(&l_ctx->lightness_srv, l_ctx->current_lvl);
    	rsp->target = l_ctx->target_lvl;
    	rsp->remaining_time = l_ctx->rem_time;
    }
    
    static const struct bt_mesh_lightness_srv_handlers lightness_srv_handlers = {
    	.light_set = light_set,
    	.light_get = light_get,
    };
    
    static struct lightness_ctx my_ctx = {
    	.lightness_srv = BT_MESH_LIGHTNESS_SRV_INIT(&lightness_srv_handlers),
    
    };
    
    static int dummy_energy_use;
    
    static int energy_use_get(struct bt_mesh_sensor_srv *srv,
    			 struct bt_mesh_sensor *sensor,
    			 struct bt_mesh_msg_ctx *ctx,
    			 struct bt_mesh_sensor_value *rsp)
    {
    	/* Report energy usage as dummy value, and increase it by one every time
    	 * a get callback is triggered. The logic and hardware for mesuring
    	 * the actual energy usage of the device should be implemented here.
    	 */
    	bt_mesh_sensor_value_from_micro(sensor->type->channels[0].format,
    					dummy_energy_use * 1000000LL, rsp);
    	dummy_energy_use++;
    
    	return 0;
    }
    
    static const struct bt_mesh_sensor_descriptor energy_use_desc = {
    	.tolerance = {
    		.negative = 0,
    		.positive = 0,
    	},
    	.sampling_type = BT_MESH_SENSOR_SAMPLING_UNSPECIFIED,
    	.period = 0,
    	.update_interval = 0,
    };
    
    static struct bt_mesh_sensor energy_use = {
    	.type = &bt_mesh_sensor_precise_tot_dev_energy_use,
    	.get = energy_use_get,
    	.descriptor = &energy_use_desc,
    };
    
    static struct bt_mesh_sensor *const sensors[] = {
    	&energy_use,
    };
    
    static struct bt_mesh_sensor_srv sensor_srv =
    	BT_MESH_SENSOR_SRV_INIT(sensors, ARRAY_SIZE(sensors));
    
    static struct bt_mesh_scene_srv scene_srv;
    
    static struct bt_mesh_light_ctrl_srv light_ctrl_srv =
    	BT_MESH_LIGHT_CTRL_SRV_INIT(&my_ctx.lightness_srv);
    
    
    static void temp_srv_set(struct bt_mesh_light_temp_srv *srv,
                             struct bt_mesh_msg_ctx *ctx,
                             const struct bt_mesh_light_temp_set *set,
                             struct bt_mesh_light_temp_status *status)
    {
        // Implement setting the temperature on your hardware
    }
    
    static void temp_srv_get(struct bt_mesh_light_temp_srv *srv,
                             struct bt_mesh_msg_ctx *ctx,
                             struct bt_mesh_light_temp_status *status)
    {
        // Implement reading the current temperature state from your hardware
    }
    
    static const struct bt_mesh_light_temp_srv_handlers temp_srv_handlers = {
        .set = temp_srv_set,
        .get = temp_srv_get,
    };
    
    static struct bt_mesh_light_temp_srv light_temp_srv =
    BT_MESH_LIGHT_TEMP_SRV_INIT(&temp_srv_handlers);
    
    
    static struct bt_mesh_light_ctl_srv light_ctl_srv =
        BT_MESH_LIGHT_CTL_SRV_INIT(&lightness_srv_handlers, &temp_srv_handlers);
    
    static struct bt_mesh_elem elements[] = {
    	BT_MESH_ELEM(1,
    		BT_MESH_MODEL_LIST(
    			BT_MESH_MODEL_CFG_SRV,
    			BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
    			BT_MESH_MODEL_LIGHTNESS_SRV(&my_ctx.lightness_srv),
    			BT_MESH_MODEL_SCENE_SRV(&scene_srv),
    			BT_MESH_MODEL_SENSOR_SRV(&sensor_srv),
    			BT_MESH_MODEL_LIGHT_CTL_SRV(&light_ctl_srv)),
    		BT_MESH_MODEL_NONE),
        BT_MESH_ELEM(2,
                     BT_MESH_MODEL_LIST(
                         BT_MESH_MODEL_LIGHT_TEMP_SRV(&light_ctl_srv.temp_srv)),
                     BT_MESH_MODEL_NONE),
        BT_MESH_ELEM(3,
                     BT_MESH_MODEL_LIST(
                         BT_MESH_MODEL_LIGHT_CTRL_SRV(&light_ctrl_srv)),
                     BT_MESH_MODEL_NONE)
    };
    
    // static struct bt_mesh_elem elements[] = {
    // 	BT_MESH_ELEM(1,
    // 		     BT_MESH_MODEL_LIST(
    // 			     BT_MESH_MODEL_CFG_SRV,
    // 			     BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
    // 			     BT_MESH_MODEL_LIGHTNESS_SRV(
    // 					 &my_ctx.lightness_srv),
    // 			     BT_MESH_MODEL_SCENE_SRV(&scene_srv),
    // 			     BT_MESH_MODEL_SENSOR_SRV(&sensor_srv)),
    // 		     BT_MESH_MODEL_NONE),
    // 	BT_MESH_ELEM(2,
    // 		     BT_MESH_MODEL_LIST(
    // 			     BT_MESH_MODEL_LIGHT_CTRL_SRV(&light_ctrl_srv)),
    // 		     BT_MESH_MODEL_NONE),
    // };
    
    static const struct bt_mesh_comp comp = {
    	.cid = CONFIG_BT_COMPANY_ID,
    	.elem = elements,
    	.elem_count = ARRAY_SIZE(elements),
    };
    
    const struct bt_mesh_comp *model_handler_init(void)
    {
    	k_work_init_delayable(&attention_blink_work, attention_blink);
    	k_work_init_delayable(&my_ctx.per_work, periodic_led_work);
    
    	return &comp;
    }
    
    void model_handler_start(void)
    {
    	int err;
    
    #if IS_ENABLED(CONFIG_BT_MESH_NLC_PERF_CONF)
    	if (bt_mesh_comp2_register(&comp_p2)) {
    		printk("Failed to register comp2\n");
    	}
    #endif
    
    	if (bt_mesh_is_provisioned()) {
    		return;
    	}
    
    	bt_mesh_ponoff_srv_set(&light_ctrl_srv.lightness->ponoff,
    			       BT_MESH_ON_POWER_UP_RESTORE);
    
    	err = bt_mesh_light_ctrl_srv_enable(&light_ctrl_srv);
    	if (!err) {
    		printk("Successfully enabled LC server\n");
    	}
    }
    
    280030.prj.conf

  • Hi Jack,
    It seems CONFIG_BT_MESH_LIGHT_TEMP_SRV is missing. 
    Could you add 
    CONFIG_BT_MESH_LIGHT_TEMP_SRV=y  to prj.conf ? 

  • I did, unfortunately I'm still getting the same behavior. 

    ...

    CONFIG_BT_MESH_LIGHT_TEMP_SRV=y
    CONFIG_BT_MESH_LIGHT_CTL_SRV=y
    ...


    I also commented out this line in model_handler.c because it was unused and seemingly redundant but still no change.

    // static struct bt_mesh_light_temp_srv light_temp_srv =

    // BT_MESH_LIGHT_TEMP_SRV_INIT(&temp_srv_handlers);

  • I'm not sure what could be wrong, my suggestion is to take a look at the test sample at \nrf\tests\bluetooth\tester. What I can see in the project is the CTL server is initialized in the model_handler.c 

Reply Children
  • Hung, I've made progress using your suggestion.  I took the nrf/tests/bluetooth/tester project model_handler.c file and rused it to replace the light_ctrl example model_handler.  I then deleted several models and some tester-dependent code, and updated prj.conf and CMakeLists.txt.  The resulting code built, ran, and provisioned, but I don't yet know if it functions.  I'm trying to get a less Frankenstein version going now.


    Edit - Actually, I'm getting a different error now at the start of provisioning, but no crash.

    Thanks,
    Jack

    *** Booting Mesh Light Fixture v2.8.0-786004901682 ***
    *** Using nRF Connect SDK v2.8.0-a2386bfc8401 ***
    *** Using Zephyr OS v3.7.99-0bc3393fb112 ***
    Initializing...
    I: 8 Sectors of 4096 bytes
    I: alloc wra: 0, fe8
    I: data wra: 0, 0
    I: SoftDevice Controller build revision:
    I: fe 2c f9 6a 7f 36 22 2e |.,.j.6".
    I: a0 79 c0 40 be 2c 03 20 |.y.@.,.
    I: 40 c2 f3 32 |@..2
    I: HW Platform: Nordic Semiconductor (0x0002)
    I: HW Variant: nRF52x (0x0002)
    I: Firmware: Standard Bluetooth controller (0x00) Version 254.63788 Build 573996906
    I: No ID address. App must call settings_load()
    Bluetooth initialized
    W: Unused space in relation list: 54
    I: Identity: EC:2C:E8:3F:5C:0E (random)
    I: HCI: version 6.0 (0x0e) revision 0x104e, manufacturer 0x0059
    I: LMP: version 6.0 (0x0e) subver 0x104e
    I: Device UUID: 5c0b7b01-159d-4da6-a3f4-84feea62c259
    Mesh initialized
    W: opcode 0x2039 status 0x0d
    E: Failed to start advertiser
    E: Advertising failed: err -12

Related