How to reduce zigbee power consumption when endpoint is searching network

We have a multiprotocol project running BLE and Zigbee Stacks. The device always operate on BLE, but Zigbee is optional. The project is developed using NCS with zephyr revision v2.4.99-ncs2. Zigbee can be enabled or disabled writing to a BLE characteristic. The device is configured as a Zigbee Endpoint

Zigbee stack is started with those lines of code:

	zb_af_register_device_ctx(&device_context);

	zb_set_ed_timeout(ED_AGING_TIMEOUT_64MIN);
	zb_set_keepalive_timeout(ZB_MILLISECONDS_TO_BEACON_INTERVAL(3000));
	zb_set_rx_on_when_idle(ZB_FALSE);

	zigbee_enable();

When zigbee stack is searching network, the device is consuming around 15 mA, and after successful steering it lows down to 100 uA. If Zigbee is enabled, consumption is OK, but when Zigbee is disabled, and no network is available, the device keeps consuming 15 mA forever.

My questions are:

¿How can I disable the Zigbee stack on runtime, and avoid the consumption part due to zigbee functions?

alternatively

¿Is there a way to simulate a network join, without having a real network, so that the Zigbee EP thinks it is connected and the consumption keeps on 100 uA?

Even if we decide not to execute zigbee_enable at start up, the fact of having CONFIG_ZIGBEE=y consumes around 1 mA. So that not enabling zigbee at start up, is not an option.

Any help will be appreciated, thanks,

Jordi

Parents
  • Hi,

    No worries about the response time. We get notified when a reply comes in on our open tickets.

    Allright, so I see you've tried a couple of functions to disable the Zigbee network and none of them works as intended, which is as expected if we take a closer look on what these functions do. 

    • zb_bdb_reset_via_local_action(0): In short terms, what this function does is to factory reset the Zigbee stack and sends the ZB_ZDO_SIGNAL_LEAVE to the network, and leaving the network. It does not disable the Zigbee. 
    • zdo_mgmt_leave_req(): This function sends a "management leave request" to a device and asks it to leave the network. It can be used to ask other devices to leave the network or send it to itself and leave the network itself. 
    • zb_sleep_now(): As far as I know this function puts the network to sleep on command, but it whenever it gets interrupted by any routines or tasks to do, the system goes out of sleep. It is only usable in a end device such as a light switch, which does not continually have tasks to do (unless it is toggled all the time). 

    What all these 3 functions have in common, is that if you try to leave the network in any manner, the first action that happens after this is done is that the device starts looking for a network again, leaving you with the power spikes whenever this process starts. 

    You're right that there are no equivalent function to zigbee_enable() in the shape of a "zigbee_disable()", which is why multiprotocol support in NCS with this Zigbee sample that supports enabling of multiprotocol support is the proper way to implement it. Over time, the power consumption caused by the action of searching and joining a network becomes far larger than to properly implement a time sliced multi-protocol application. Doing it this way you may take advantage of the functionality that negotiates timeslots for the 802.15.4 radio driver and ensure that the various protocols have allocated enough time to the tasks required by them such as discovering, joining, and performing other various tasks in the queue before going to sleep and being awoken. 

    Feel free to follow up with more questions and let me know if this clarifies anything for you!

    Kind regards,
    andreas

  • Hi Andreas,

    Thanks for your answer. I attached my code here for the zboss_signal_handler and for the function start, that starts the zigbee stack. You can observe is like in the example Multiprotocol Light Switch . The Sleepy End Device behavior is enabled with the function zb_set_rx_on_when_idle(ZB_FALSE):

    void zboss_signal_handler(zb_bufid_t bufid)
    {
    	ZigbeeDeviceContext* instance = ZigbeeDeviceContext::getInstance();
    
    	zb_zdo_app_signal_hdr_t* p_sg_p = nullptr;
    	zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &p_sg_p);
    	zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid);
    
    	LOG_DBG("sig = %d", sig);
    
    	ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
    
    	if(sig == ZB_BDB_SIGNAL_STEERING || sig == ZB_BDB_SIGNAL_DEVICE_REBOOT) {
    		if(status == RET_OK) {
    			instance->joinedSuccessful();
    		}
    	} else if(sig == ZB_COMMON_SIGNAL_CAN_SLEEP) {
    		if(instance->isJoined() == true) {
    			zb_sleep_now();
    		}
    	}
    	/* All callbacks should either reuse or free passed buffers.
    	 * If bufid == 0, the buffer is invalid (not passed).
    	 */
    	if(bufid) {
    		zb_buf_free(bufid);
    	}
    }
    
    void ZigbeeDeviceContext::start()
    {
    	ZB_ZCL_REGISTER_DEVICE_CB(commandHandler);
    
    	if(endpoints.empty() == false) {
    		device_context.ep_count = endpoints.size();
    		device_context.ep_desc_list = new zb_af_endpoint_desc_t*[device_context.ep_count];
    		int i = 0;
    		for(const auto& endpoint: endpoints) {
    			device_context.ep_desc_list[i] = endpoint.second->getDescriptor();
    			i++;
    		}
    	}
    
    	zb_af_register_device_ctx(&device_context);
    
    	zb_set_ed_timeout(ED_AGING_TIMEOUT_64MIN);
    	zb_set_keepalive_timeout(ZB_MILLISECONDS_TO_BEACON_INTERVAL(3000));
    	zb_set_rx_on_when_idle(ZB_FALSE);
    
    	zigbee_enable();
    }
    

    Now, I have just discovered than if I comment line 330 of file zigbee_app_utils.c:

    // 				start_network_rejoin();
    

    The system behaves as desired, that is, consumption is high 17mA for 40 seconds, while end device is trying to join network, and is low 150 uA, after those 40 seconds.

    So, my conclusion is that the fact that the end device is continuously trying to join to a zigbee network is the responsible for high consumption.

    I don't like the solution of commenting line 330, because zigbee_app_utils.c belongs to the SDK. So, do you think, there is another way of avoiding a continuous network rejoin try (change CONFIG, call to a function, ... any idea?)

    Thanks,

    Jordi

  • Hi,

    Jorcis said:
    So, my conclusion is that the fact that the end device is continuously trying to join to a zigbee network is the responsible for high consumption.

    This is correct.

    Jorcis said:
    I don't like the solution of commenting line 330, because zigbee_app_utils.c belongs to the SDK

    I also agree with this. It is possible to edit and change the SDK files as you see fit, and some scenarios may require developers to do it, but personally I like the mentality of trying to fix it from the top down first, rather than changing the foundation through personal, customized hacks.

    Jorcis said:
    So, do you think, there is another way of avoiding a continuous network rejoin try (change CONFIG, call to a function, ... any idea?)

    One option you could implement to get this behavior is to override the zigbee_default_signal_handler function, which gets called within "void zboss_signal_handler(...)" in the light switch sample, line 439. If you create something that overrides the error_check that calls the default signal handler, it should avoid going through the motions of searching for and rejoining a network. I don't have a sample that illustrates this, but I believe it should be a relatively simple change to implement.

    Let me know if this works out for you!


    Kind regards,
    Andreas

Reply
  • Hi,

    Jorcis said:
    So, my conclusion is that the fact that the end device is continuously trying to join to a zigbee network is the responsible for high consumption.

    This is correct.

    Jorcis said:
    I don't like the solution of commenting line 330, because zigbee_app_utils.c belongs to the SDK

    I also agree with this. It is possible to edit and change the SDK files as you see fit, and some scenarios may require developers to do it, but personally I like the mentality of trying to fix it from the top down first, rather than changing the foundation through personal, customized hacks.

    Jorcis said:
    So, do you think, there is another way of avoiding a continuous network rejoin try (change CONFIG, call to a function, ... any idea?)

    One option you could implement to get this behavior is to override the zigbee_default_signal_handler function, which gets called within "void zboss_signal_handler(...)" in the light switch sample, line 439. If you create something that overrides the error_check that calls the default signal handler, it should avoid going through the motions of searching for and rejoining a network. I don't have a sample that illustrates this, but I believe it should be a relatively simple change to implement.

    Let me know if this works out for you!


    Kind regards,
    Andreas

Children
  • Hi Andreas,

    The following zboss_signal_handler implementation works:

    void zboss_signal_handler(zb_bufid_t bufid)
    {
    	ZigbeeDeviceContext* instance = ZigbeeDeviceContext::getInstance();
    
    	zb_zdo_app_signal_hdr_t* p_sg_p = nullptr;
    	zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &p_sg_p);
    	zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid);
    	zb_ret_t ret_code = RET_OK;
    
    	if(sig == ZB_BDB_SIGNAL_STEERING && status != RET_OK) { /**< Avoid start_network_rejoin */
    		instance->joinedUnsuccessful();
    		LOG_INF("Network steering was not successful (status: %d)", status);
    	} 
    	else if(sig == ZB_BDB_SIGNAL_DEVICE_REBOOT && status != RET_OK) { /**< Stored PAN-ID not found, avoid start_network_rejoin */
    		instance->joinedUnsuccessful();
    		LOG_INF("Unable to join the network (status: %d", status);
    	}
    	else {
    		ret_code = zigbee_default_signal_handler(bufid);
    	}
    
    	if(ret_code == RET_OK) {
    		if(sig == ZB_BDB_SIGNAL_STEERING || sig == ZB_BDB_SIGNAL_DEVICE_REBOOT) {
    			if(status == RET_OK) {
    				instance->joinedSuccessful();
    			}
    		} else if(sig == ZB_COMMON_SIGNAL_CAN_SLEEP) {
    			if(instance->isJoined() == true) {
    				zb_sleep_now();
    			}
    		}
    	}
    
    	/* All callbacks should either reuse or free passed buffers.
    	 * If bufid == 0, the buffer is invalid (not passed).
    	 */
    	if(bufid) {
    		zb_buf_free(bufid);
    	}
    }
    

    On the other hand, my application requires enabling and disabling Zigbee via a BLE smartphone APP, but it is tough using actual zboss api, without compromising power consumption. There is neither zigbee_disable function nor anything suitable for that purpose. Until now, the only solution which I don't like, is to reboot system at every enable / disable change.

    Should you have  any idea or example of how to enable/disable Zigbee at runtime, without compromising sleepy mode and power consumption, it will be much appreciated.

    Thanks,

    Jordi 

  • Hi Jordi,

    Glad to hear that you have managed to get it to work, although with a somewhat hacky-solution!

    A couple of questions back at you:

    1) I have been right in assuming that the application you are developing is for an end device? It has not been explicitly stated so far, only implied through you working with the light switch sample.

    2) Did you read the multiprotocol documentation supplied and to what degree did you follow it? I'll be glad to help you further with questions regarding this if there was anything you did not understand in this resource! 

    Jorcis said:
    Should you have  any idea or example of how to enable/disable Zigbee at runtime, without compromising sleepy mode and power consumption, it will be much appreciated.

    There are currently no "easy" solution for enabling/disabling ZigBee at runtime, as has been stated by both me and you several times, but the multiprotocol sample and documentation I have supplied contains a solution that handles the issues you're struggling with in a different way. The only other hacky solution is what you've started with by overriding the signal handler or forcing the device to leave the network, and after this don't do anything more ZigBee-related unless told so. By default it will try to rejoin the network, which will cause the power consumption spike mentioned earlier, so you will need to disable this as well. If you manage to remove this "auto-rejoin-feature" it should be possible to implement something that triggers the enabling/rejoining whenever you send a signal over BLE on command.

    But I must add that I can not guarantee that we will be able to support your own custom multiprotocol solution in the future if it deviates far from our own supported multiprotocol solution, so keep that in mind to some degree!

    Kind regards,
    Andreas

  • Hi Andreas

    1) I have been right in assuming that the application you are developing is for an end device? It has not been explicitly stated so far, only implied through you working with the light switch sample.

    Yes you are right, I am developing for an end device. Sorry about that, now I realize I wrote down endpoint where I wanted to say end device.

    2) Did you read the multiprotocol documentation supplied and to what degree did you follow it? I'll be glad to help you further with questions regarding this if there was anything you did not understand in this resource!

    Yes, I have read the supplied documentation but, I have not entered into detail. I have taken some interesting pics about it, like the zigbee_configure_sleepy_behavior(true). You have done great job about the multiprotocol feature, congrats. I would like to understand a bit more about that, but there's some pressure about releasing an MVP, and I am not able to spend time with a deep reading and investigation. I tend to focus on the most practical aspects, being a mere user of the SDK API, although I don't discard to get into more detail in the future, I will let you know If I have further questions.

    We don't use BLE NUS as in the light_switch example, on the contrary our device is a peripheral BLE with a GATT table, with connection and disconnection from another central device. I would like to note than when we introduced Zigbee and multiprotocol, it worked without significant modifications, so that was amazing, and in some kind magic!

    Also, on the other hand, in the light_switch example I have observed pairing callbacks, and I wonder how it works in BLE:

    	.pairing_confirm = pairing_confirm,
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    

    I wonder if it would be possible to pair BLE peripheral and central device, automatically when they get closer one another. There's a possibly general misunderstanding in our department that pairing is only possible between two legacy traditional bluetooth devices but not between low energy ones.

    Anyway, many thanks for you kind help and support.

    Jordi.

  • Hi Jordi,

    1) and 2): Great! Then we're on the same page! It is easy to focus on the practical aspects about something, and not spend enough time on reading and understanding the theory/API and I must admit I really know the feeling of prioritizing one over the other!..

    Jorcis said:
    I wonder if it would be possible to pair BLE peripheral and central device, automatically when they get closer one another.

    Yes, this is possible, and it can be done by modifying the peripheral_uart and central_uart filters. The BLE scan API should give you a headstart on how to do this together with those two samples. What you want to do is to limit the scanning of devices with a filter such that the peripheral only advertises a given filter (it could be UUID or a name in form of a string), and that the central and peripheral should only connect to each other if that filter criteria is met

    You could get started with this documentation and the two samples, and I'll see if I can put together a small sample illustrating this tomorrow!

    Kind regards,
    Andreas

  • Hi again, following up from yesterdays post

    Jorcis said:
    There's a possibly general misunderstanding in our department that pairing is only possible between two legacy traditional bluetooth devices but not between low energy ones.

    Yes, this is a misunderstanding. Both bonding and pairing can be done with both legacy Bluetooth protocol and newer Low Energy protocols. I said I was going to create a small sample to illustrate this, but I think the peripheral_bms sample which uses the Bluetooth LE bond module or the central and peripheral hrs, sample which scans for peripherals with the heart rate service defined, explains this far better. Here you may also see how the pairing callback structs are done line 128 in the main file in peripheral_bms as well as 202 in central_and_peripheral_hrs with with their respective 

    I hope this answers the missing details from my reply yesterday!

    Kind regards,
    Andreas

Related