Cleode.fr
Cleode.fr
La domotique facile et sans fil

Librairie ZCL 4.3 C++

Table of contents

  1. ZCL Library overview
  2. Starting communication with an UBee
  3. Subscribe to receive node events
  4. Sending a Read attributes command
  5. Sending a Write attributes command
  6. Sending a Discover attributes command
  7. Sending a Read reporting configuration command
  8. Sending a Configure reporting command
  9. Sending a Cluster Specific command
  10. Sending a ZCL Frame command
  11. Subscribe to receive notification events
  12. Create a Binding to receive notifications
  13. Create a Binding to actuate a device
  14. Set the path of the license file
  15. Set the license key by programming

1. ZCL Library overview

The ZCL library API allows facilitating the development of application which interfaces with the Cleode UBee™ dongle.
It includes a C++ dynamic library (".dll" for Windows, ".so" for Linux or ".dynlib" for MacOSX) and include files.

This API provides services for the following functionalities:

  • Management and configuration of UBee.
  • Reception of association events, update of Zigbee nodes.
  • Reading of present Zigbee nodes in the network.
  • Emission and reception of commands ZCL.
  • Subscription to receive notifications of Zigbee nodes.
  • Creation and deletion of bindings between Zigbee nodes.
  • Authorization of Zigbee nodes to be gone into the network.

2. Starting communication with an UBee

The detection of UBee on any USB port is automatic, except in the case where the port name is specified.
The return value of startUbee() service indicates if the UBee is detected or not.

When UBee is detected, an UBEE_STARTED event is notified and you can check the license validity.
If UBee is unplugged, an UBEE_RELEASED event is notified. And thereafter every 5 seconds, the API verify if UBee is replugged.
When you call the stopUbee() service, an UBEE_STOPPED event is notified.
If UBee is awaiting reprogramming, an UBEE_WAITING_BOOT event is notified.

void ubeeEventsCallback(zcl::UbeeEvent ubeeEvt)
{
	printf("===========> Ubee %s notification\n",
			(ubeeEvt == zcl::UBEE_STARTED ? "Started" :
			(ubeeEvt == zcl::UBEE_RELEASED ? "Released" :
			(ubeeEvt == zcl::UBEE_STOPPED ? "Stopped" : "Waiting Boot"))));
			
	if (ubeeEvt == zcl::UBEE_STARTED) {
		printf("===========> UBee License validity for %s : %s\n",
			zcl::getCoordinator().c_str(), zcl::isLicenseValid() ? "true" : "false" );
	}
}

int main(int argc, char * argv[])
{
	bool result = zcl::startUbee(ubeeEventsCallback);
	printf("UBee is %s \n", (result ? "detected" : "not detected"));

	printf("Press any key to Quit the application\n");
	std::cin.get();
	
	// Stop the communication with UBee
	zcl::stopUbee();
}
		

Starting UBee on a specific port name

// Windows
zcl::startUbee(ubeeEventsCallback, "COM22");

// Linux
zcl::startUbee(ubeeEventsCallback, "/dev/ttyUSB1");
		

3. Subscribe to receive node events

This subscription allows to receive the events such as associating, updating and deleting of the Zigbee nodes.
It is strongly recommended to make a clone of the node if you want to store the node instance and to modify it.

After the UBee is starting, a discovery of Zigbee node is done and at least 3 events are notified for each node.
  - When the structure of the node has been read : Created / Discover.
  - When the model identifier of the node has been read : Updated / ModelID.
  - When the bindings of the node has been read : Updated / Bindings.

std::string formatReason(zcl::Reason reason)
{
	switch (reason) {
		case zcl::UNKNOWN: return "Unknown";
		case zcl::ANNOUNCE: return "Announce";
		case zcl::DISCOVER: return "Discover";
		case zcl::MODEL_ID: return "ModelID";
		case zcl::BINDINGS: return "Bindings";
		case zcl::BIND_CMD: return "BindCmd";
		case zcl::PARENT: return "Parent";
		case zcl::CHILDREN: return "Children";
		case zcl::LEAVE: return "Leave";
		case zcl::RESET: return "Reset";
		case zcl::UNPLUG: return "Unplug";
		case zcl::STOP: return "Stop";
		case zcl::NAME: return "Name";
	}
	return "Undefined";
}
		
void nodeEventsCallback(zcl::NodeEvent nodeEvt, const ZNode& node, zcl::Reason reason)
{
	printf("====> Node %s %s \n", 
		(nodeEvt == zcl::EVENT_CREATE ? "Created" :
		(nodeEvt == zcl::EVENT_UPDATE ? "Updated" : "Deleted")),
		node.toString().c_str(), formatReason(reason).c_str());
}

...

int main(int argc, char * argv[])
{
	// Subcribe to receive node events
	zcl::registerCallback(nodeEventsCallback);
	
	// Start the communication with UBee
	zcl::startUbee(ubeeEventsCallback);

	printf("Press any key to Quit the application\n");
	std::cin.get();
	
	// Stop the communication with UBee
	zcl::stopUbee();
}
        

4. Sending a Read attributes command

The Read Attributes command is used to read the attribute values of a cluster from the device.
The response of this request is received on the callback passed as parameter to the service.

A timeout expires if the device is not responding to the request.
The timeout period for router is 15 seconds by default, and 30 seconds for end-devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const ReadAttributeResponse attributes[], int size)
	{
		if (size > 0) {
			// Responses received
			for (int i = 0; i < size; i++) {
				if (attributes[i].getStatus() == Status::SUCCESS) {
					// Successful reading of this attribute
				} else {
					// Unsuccessful reading of this attribute
				}
			}
		} else {
			// Timeout expired !
		}
	}
	...
};

// Listener instance which receives responses 	
ResponseReceiver receiver;	

...
	
// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pNode = zcl::searchNode(ieeeAddr);

Attribute attrs[4];
attrs[0].setIdentifier(AttrConstants::ZCL_VERSION_ATTR);
attrs[1].setIdentifier(AttrConstants::APP_VERSION_ATTR);
attrs[2].setIdentifier(AttrConstants::STACK_VERSION_ATTR);
attrs[3].setIdentifier(AttrConstants::HW_VERSION_ATTR);

// Read the fourth first attributes of the Basic cluster
byte seqNumber = zcl::sendReadAttributesCommand(&receiver, pNode->getNwkAddr(),
	pNode->getEpByInCluster(Cluster::BASIC_ATTRIBUTES),
	Cluster::BASIC_ATTRIBUTES, attrs, 4);
        

5. Sending a Write attributes command

The Write Attributes command is used to write/modify the attribute values of a cluster into the device.
The response of this request is received on the callback passed as parameter to the service.

A timeout expires if the device is not responding to the request.
The timeout period for router is 15 seconds by default, and 30 seconds for end-devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const WriteAttributeResponse attributes[], int size)
	{
		if (size > 0) {
			// Responses received
			for (int i = 0; i < size; i++) {
				if (attributes[i].getStatus() == Status::SUCCESS) {
					// Successful writing of this attribute
				} else {
					// Unsuccessful writing of this attribute
				}
			}
		} else {
			// Timeout expired !
		}
	}
	...
};
	
// Listener instance which receives responses 	
ResponseReceiver receiver;	

...
	
// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pNode = zcl::searchNode(ieeeAddr);

// Fill the attribute to be written
WriteAttribute attr;
attr.setIdentifier(AttrConstants::LOCATION_DESCRIPTION_ATTR);
attr.setDataType(CHARACTER_STRING);
attr.setDataAsString("Office");

// Write the location description attribute of this device
byte seqNumber = zcl::sendWriteAttributesCommand(&receiver, pNode->getNwkAddr(),
	pNode->getEpByInCluster(Cluster::BASIC_ATTRIBUTES),
	Cluster::BASIC_ATTRIBUTES, &attr, 1);
		

6. Sending a Discover attributes command

The Discover Attributes command is used to know the attributes of a cluster implemented by the device.

A timeout expires if the device is not responding to the request.
The timeout period for router is 15 seconds by default, and 30 seconds for end-devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const DiscoveredAttribute attributes[], int size)
	{
		if (size > 0) {
			// Responses received
			for (int i = 0; i < size; i++) {
				printf("AttrId(0x%04x) - DataType(%s)\n",
					attributes[i].getIdentifier(),
					attributes[i].getDataType().name());
			}
		} else {
			// Timeout expired !
		}
	}
	...
};
	
// Listener instance which receives responses 	
ResponseReceiver receiver;	

...
	
// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pNode = zcl::searchNode(ieeeAddr);

// Send a Discover Attributes command to discover the attributes
// of Basic cluster implemented by this device
byte seqNumber = zcl::sendDiscoverAttributesCommand(&receiver, pNode->getNwkAddr(),
	pNode->getEpByInCluster(Cluster::BASIC_ATTRIBUTES),
	Cluster::BASIC_ATTRIBUTES, AttrConstants::ZCL_VERSION_ATTR, 20);
		

7. Sending a Read reporting configuration command

The Read Reporting Configuration command is used to read the configuration details of the reporting mechanism for one or more of the attributes of a cluster.

A timeout expires if the device is not responding to the request.
The timeout period for router is 15 seconds by default, and 30 seconds for end-devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const ReadAttributeResponse attributes[], int size)
	{
		if (size > 0) {
			// Responses received
			for (int i = 0; i < size; i++) {
				if (attributes[i].getStatus() == Status::SUCCESS) {
					// Successful reading of the reporting configuration of this attribute
				} else {
					// Unsuccessful reading of the reporting configuration of this attribute
				}
			}
		} else {
			// Timeout expired !
		}
	}
	...
};
	
// Listener instance which receives responses 	
ResponseReceiver receiver;	

...

// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZrc = zcl::searchNode(ieeeAddr);

// Fill the attribute for which we want to read the reporting configuration
ReadReportingConfigAttribute attr;
attr.setDirection(ReportingDirection::REPORTS_RECEIVED);
attr.setIdentifier(AttrConstants::MEASURED_VALUE_ATTR);
		
// Read the reporting configuration 
byte seqNumber = zcl::sendReadReportingConfigurationCommand(&receiver, pZrc->getNwkAddr(),
	pZrc->getEpByInCluster(Cluster::TEMPERATURE_MEASUREMENT),
	Cluster::TEMPERATURE_MEASUREMENT, &attr, 1);
		

8. Sending a Configure reporting command

The Configure Reporting command is used to configure the reporting mechanism for one or more of the attributes of a cluster.

A timeout expires if the device is not responding to the request.
The timeout period for router is 15 seconds by default, and 30 seconds for end-devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const ReportingConfigurationAttributeResponse attributes[], int size)
	{
		if (size > 0) {
			// Responses received
			for (int i = 0; i < size; i++) {
				if (attributes[i].getStatus() == Status::SUCCESS) {
					// Successful writing of the reporting configuration of this attribute
				} else {
					// Unsuccessful writing of the reporting configuration of this attribute
				}
			}
		} else {
			// Timeout expired !
		}
	}
	...
};
	
// Listener instance which receives responses 	
ResponseReceiver receiver;	

...
		
// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZrc = zcl::searchNode(ieeeAddr);

// Fill the attribute for which we want to write the reporting configuration
ReportingConfigurationAttribute attr;
attr.setDirection(ReportingDirection::REPORTS_RECEIVED);
attr.setIdentifier(AttrConstants::MEASURED_VALUE_ATTR);
attr.setDataType(SIGNED_16BITS);
attr.setMinReportingInterval(300);
attr.setMaxReportingInterval(3600);
attr.setReportableChangeAsNumber(100);
		
// Write the reporting configuration 
byte seqNumber = zcl::sendConfigureReportingCommand(&receiver, pZrc->getNwkAddr(),
	pZrc->getEpByInCluster(Cluster::TEMPERATURE_MEASUREMENT),
	Cluster::TEMPERATURE_MEASUREMENT, &attr, 1);
		

9. Sending a Cluster Specific command

The Cluster Specific command is used to send a specific command to a cluster.
Look in the Zigbee specfication to know which cluster supports Cluster Specific command.

Examples:

  • On, Off or Toggle commands for On/Off Cluster
  • Identify or Identify Query commands for Identify Cluster
  • Lock Door or Unlock Door commands for Door Lock Cluster
  • etc.

// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZPlug = zcl::searchNode(ieeeAddr);

// Send a Toggle command to a Plug device
zcl::sendClusterSpecificCommand(pZPlug->getNwkAddr(),
	pZPlug->getEpByInCluster(Cluster::ON_OFF),
	Cluster::ON_OFF, AttrConstants::TOGGLE_COMMAND, NULL, 0);
		

10. Sending a ZCL Frame command

The ZCL frame command is the generic command that covers all other commands.

For example:
if you want to toggle the state of a ZPlug and verify that the ZPlug received the command and well decoded it,
you have to use this generic command .

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const ZCLFrame * frame)
	{
		if (frame) {
			if (frame->getPayload()->isDefaultResponse()) {
				const DefaultResponsePayload * payload = 
												frame->getPayload()->getDefaultResponsePayload();
				if (payload->getStatus() == Status::SUCCESS) {
					// Successful decoding of the Toggle command
				} else {
					// Unsuccessful decoding of the Toggle command
				}
			}
		} else {
			// Timeout expired !
		}
	}
	...
};

// Listener instance which receives responses
ResponseReceiver receiver;	

...
		
// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZPlug = zcl::searchNode(ieeeAddr);

// Fill the ZCL Frame request
ZCLFrame * frame = ZCLFrame::newZCLFrameInstance();
frame->getHeader()->getFrameControl()->setFrameType(FrameType::SPECIFIC_CLUSTER);
frame->getHeader()->getFrameControl()->setDirection(Direction::CLIENT_TO_SERVER);
frame->getHeader()->getFrameControl()->setManufacturerCode(
												ManufacturerCode::NOT_INCLUDE_IN_ZCLFRAME);
frame->getHeader()->getFrameControl()->setResponseStatus(ResponseStatus::RESPONSE_RETURNED);
frame->getHeader()->setCmdIdentifierAsValue(AttrConstants::TOGGLE_COMMAND);
	
// Send a Toggle command to a ZPlug device and verify
// that the ZPlug received the command and well decoded it
zcl::sendZCLFrameCommand(&receiver, pZPlug->getNwkAddr(),
	pZPlug->getEpByInCluster(Cluster::ON_OFF),
	Cluster::ON_OFF, *frame);
delete frame;
		

11. Subscribe to receive notification events

The notification events is based on the Observer pattern (also known as Listener in Java).
You have to implement the virtual NotificationListener class for consuming the events.
You have also to create a Binding in the device to UBee

The Report Attribute is a notification sent by a device in according to the Reporting Configuration of the attribute.

The Cluster Specific command can be regarded as a notification sent by a device for any Cluster such as IAS Zone, Alarm, etc.

class NotificationReceiver : public NotificationListener
{
public:	
	void notifyReportAttribute(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		const ReportAttribute attributes[], int size)
	{
	}

	void notifyClusterSpecificCommand(
		dbyte nwkAddr, byte epNumber, dbyte clusterId, byte seqNumber,
		byte cmd, const ClusterSpecCmdAttribute attributes[], int size)
	{
	}
};

// Listener instance which receives notifications		
NotificationReceiver receiver;
		

You can subscribe to receive notifications from all devices.

	
// Subscribe to receive notification events from all devices
zcl::subscribe(&receiver);

...

// Unsubscribe to receive notification events from all devices
zcl::unsubscribe(&receiver);
		

Or you can subscribe to receive notifications from only one device.

// Search a device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pNode = zcl::searchNode(ieeeAddr);

// Subscribe to receive notification events from only this device
zcl::subscribe(&receiver, pNode->getNwkAddr());
		
...

// Unsubscribe to receive notification events from only this device
zcl::unsubscribe(&receiver, pNode->getNwkAddr());
		

12. Create a Binding to receive notifications on UBee

For receiving notifications for attributes of a cluster from a device, you have to create a binding from this device to UBee.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(dbyte nwkAddr, ResponseListener::BindCmd cmd, Status::Value status)
	{
		if (status == Status::SUCCESS) {
			const ZNode * pNode = zcl::searchNode(nwkAddr);
			if (pNode) {
				int size;
				const ZBinding * bindings = pNode->getBindings(size);
				// Display the bindings 
				for (int i = 0; i < size; i++) {
				}
			}
		}
	}
	...
};

// Search a ZRC device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZrc = zcl::searchNode(ieeeAddr);

// Search the UBee by its network address
const ZNode * pUbee = zcl::searchNode(ZNode::COORDINATOR_NWK_ADDR);

// Create a binding to receive notification from ZRC on UBee 
zcl::sendBindRequest(&receiver, pZrc->getNwkAddr(),
	pZrc->getIEEEAddress(),
	pZrc->getEpByInCluster(Cluster::TEMPERATURE_MEASUREMENT),
	Cluster::TEMPERATURE_MEASUREMENT,
	pUbee->getIEEEAddress(),
	pUbee->getEpByOutCluster(Cluster::TEMPERATURE_MEASUREMENT));
		

13. Create a Binding to actuate a device

For actuating a device by a remote control such as ZRC, ZSC or ZKey, you have to create a binding between this two devices.

class ResponseReceiver : public ResponseListener
{
public:	
	void responseReceived(dbyte nwkAddr, ResponseListener::BindCmd cmd, Status::Value status)
	{
		if (status == Status::SUCCESS) {
			const ZNode * pNode = zcl::searchNode(nwkAddr);
			if (pNode) {
				int size;
				const ZBinding * bindings = pNode->getBindings(size);
				// Display the bindings 
				for (int i = 0; i < size; i++) {
				}
			}
		}
	}
	...
};

// Search a ZRC device by its IEEE address
ZIEEEAddress ieeeAddr("11-22-33-44-55-66-77-88");
const ZNode * pZrc = zcl::searchNode(ieeeAddr);

// Search a ZPlug device by its IEEE address
const ZNode * pZPlug = zcl::searchNode(ZIEEEAddress("12-23-34-45-56-67-78-89"));

// Create a binding to control the ZPlug by the ZRC 
zcl::sendBindRequest(&receiver, pZrc->getNwkAddr(),
	pZrc->getIEEEAddress(),
	pZrc->getEpByOutCluster(Cluster::ON_OFF),
	Cluster::ON_OFF,
	pZPlug->getIEEEAddress(),
	pZPlug->getEpByInCluster(Cluster::ON_OFF));
		

You can also control several devices belonging to the same group by creating a binding in the ZRC to a group ID.

dbyte groupID = 0x0001;

// Create a binding to control the devices of the group 0x0001 by the ZRC 
zcl::sendBindRequest(&receiver, pZrc->getNwkAddr(),
	pZrc->getIEEEAddress(),
	pZrc->getEpByOutCluster(Cluster::ON_OFF),
	Cluster::ON_OFF, groupID);
		

14. Set the path of the license file

If you are unable or do not wish to put the license file in the same directory of your application, you can specify the directory for searching the license file.
The path initialization have to be done before the "startUbee() call.

// Set the path of the license file
zcl::setLicensePath("/usr/local/myappli");

// Start the communication with UBee
zcl::startUbee(ubeeEventsCallback);
		

15. Set the license key by programming

If you are unable or do not wish to read the license key in the file, you can set the license key by programming.
The license initialization have to be done before the "startUbee() call.

// Set the license key by programming
zcl::setLicenseKey("AAAAABBBBBCCCCCDDDDDEEEEE");

// Start the communication with UBee
zcl::startUbee(ubeeEventsCallback);
		
remonter