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:
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");
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(); }
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);
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);
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);
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);
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);
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:
// 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);
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;
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());
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));
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);
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);
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);