So now it’s time to make the physical interface for our light and switch. While being able to send and see messages go across the network is fun, since we are making a light and a switch, we should expect them to act like a light, turning a light source on and off and a switch, taking an input from a button press or similar signal.
First let’s turn our attention to the light, since a light turning on and off is an easier goal to observe. As we have noticed on our last step, we have been toggling one of our cluster attributes, specifically an on-off attribute. What we want is to have our device change based on this attribute’s value. One mechanism that we can use to handle this is through a callback.
A callback is a way that EmberZNet handles events throughout the stack code and application layer as well. If you click on the callbacks tab, you will see a number of calls that can be used throughout your code. These are generally broken down into application callbacks, plugin callbacks, stack callbacks, API callback and cluster callbacks. An in depth look at the callbacks is beyond the scope of this lesson.
For our light code, we are going to need to handle two events. First, at startup we need to enable our lights and configure them. Once that’s done we need to handle the toggling of the light based on the On/Off attribute.
To setup our light, we need to look at the section called “Non-cluster related.” All of these callbacks are in order. As you look down the list you will come across two callbacks Main Init and Main Start, both of these callbacks run at the start of our main() function. Main Init runs right after main executes, but before the network is setup, while Main Start runs right before our main loop executes, after the HAL is done configuring the hardware. In most cases it won’t matter where we set up our LED interface, so for this we will use Main Start, check the box for it.
Next, we need to figure out how to toggle our light on and off. Because this is tied to the on/off cluster, you want to go to the On/Off cluster under the General. Looking through the list, we can find the Server Attribute Changed callback. Since we are operating on the attribute value setting, this is perfect for our needs, check its box as well.
Before we leave this page, make sure at the bottom of your callbacks page you have the “Generate project-specific callbacks file” box checked. This will ensure your new callbacks file is generated for your application.
Before we click generate, we also need to configure all of our GPIOs to work as LEDs. The Thunderboard has 1 green LED, 1 red LED and 4 full color LEDs, for this project we are going to use the full color LEDs. If you read UG309 section 3.4.8, you will see all of the pin definitions, as well as an explanation of how the LEDs are setup.
Next, we need to launch Hardware Configurator (HWConf for short). If you look in your light project, you should see a file named brd4166a_efr32mg12p332f1024gl125.hwconf, this is your header file for your project. Double click on it and it should open in HWConf in the DefaultMode Port I/O. If it isn’t in that format, click that tab at the bottom of the DefaultMode Peripherals window. This view brings us to a graphical representation of the pins, it is where we will set our GPIOs to be available to us within our software.
Setting the GPIOs from within HWConf is fairly straight forward. First you need to go to the Outline view on the top right corner of Studio and open the Port I/O tree. Then click on the Port (A-K) you want to set, this will make those GPIOs become active on the graphical pin grid. Locate the pin you want to setup on the grid (be careful, the pin letters are easy to get confused with their grid identifiers) and click on them. You will then see the Properties of this pin on the middle right of Studio. If you click on the value area within Custom pin name, you will be able to give the pins a name, this will be how you identify the pin in your program.
For example: PJ14 is RGB_LED_ENABLE (note this is configured by default)
Do the same for the following pins – locations provided for easy locating:
Remember all of the names you give these pins.
Once this is done, save your HWConf file, this might take some time as it will generate some new hardware configurations. Then return to Simplicity IDE view and click on your MyLight.isc file. Click the Generate button. After some time you will get a validation window to appear. Make sure that all files on this window are checked, Studio will many times leave the *_callbacks.c file unchecked to not overwrite this. Once all the boxes are checked, click OK to continue.
Finally, it is time to put in some code to make our lights turn on and off. First, locate the callbacks file in your project list. It will be called PROJECTNAME_callbacks.c, in our case, this will be MyLight_callbacks.c, double click on it and the code in this file will appear. You will note that you should have 2 additional functions in the project: emberAfMainStartCallback and emberAfOnOffClusterServerAttributeChangedCallback.
First let's enable our lights. Since we need to enable our LEDs as our code starts, we will focus our attention to the emberAfMainStartCallback() function. As noted, this function is called right before our program initiates it's loop while it is running.
Now we will need a fuction to initialize our GPIOs, configuring them as output pins. You can look through docs.silabs.com and find the EMLIB section to discover a function to run this setup. To save you some time, the function you are looking for is:
void GPIO_PinModeSet (GPIO_Port_TypeDef port, unsigned int pin, GPIO_Mode_TypeDef mode, unsigned int out)
The function call for this is defined fully here. But we will step you through the function calls for easy reference.
The first two arguments are the port and pin that we want to configure. Because we have defined our pins in HWConf, you can use those names you assigned these pins in the GUI. They will be something like PIN_NAME_PORT and PIN_NAME_PIN, which correspond do the port letter and port number respectively. If you look in your project tree there is a folder called hal-config and in that folder you will find a file hal-config.h, this file contains the names of the ports and pins for easy reference, locate the section
// $[Custom pin names]
The next argument is how the pin is configured. Fortunately all of our GPIOs are set as outputs and our GPIO mode is going to be gpioModePushPull. A full list of pin options can be found here.
Finally because we are using these as output pins, the last argument is the desired initial state of the pin, either 0: off or 1: on.
So to set up RGB_LED_ENABLE, use the following:
GPIO_PinModeSet(RGB_LED_ENABLE_PORT, RGB_LED_ENABLE_PIN, gpioModePushPull, 1);
Do the same for all of your ports and pins. You should certainly make sure that RGB_LED_ENABLE is on at startup. If you want a particular color, the colored LEDs should be enabled as you want for the color you want to output, red, green or blue, or some combination of them all. LEDs 0-3 can be disabled or enabled, they are what we are going to use to turn on the LEDs.
One final warning the LEDs are quite bright, especially if you turn on all four for white, so be careful when looking at them. After doing so, you might decide you want to start with them off.
At this point you can re-compile your code and test to see if your lights come on. Once you have verified that your LEDs are working, you can then implement the on/off feature.
So now we will move down to the function emberAfOnOffClusterServerAttributeChangedCallback. This function is called anytime a server sided attribute on the light changes. It has two variables in its definition which are the endpoint that is being operated on by the change and the attribute itself which is being changed. In our case we want to change anytime we are seeing a call on our defined endpoint on the on/off attribute and change to the state which is set in that attribute. Despite only having a single endpoint in our project, you should wrap this call to make sure you are calling it on a particular endpoint, because you can then expand it to work for additional endpoint. Since our light functionality is our primary endpoint, you should compare the endpoint passed to this function against this. EmberZnet provides a means of getting this with the function emberAfPrimaryEndpoint(), this means we know that we are getting this called against the main light endpoint.
if (endpoint == emberAfPrimaryEndpoint())
Once we have done that, we need to make sure we are operating on the on/off attribute. First you should look at the file attribute-id.h. This file lists all of the attributes in your project and sets a number of defines for these attributes, using these your code will be much more readable. If you go to the section with the On/off cluster, you will see the ZCL_ON_OFF_ATTRIBUTE_ID, which matches that attribute ID. You can then do some comparison or switch statement and use that as the entry into your function.
if(attributeId == ZCL_ON_OFF_ATTRIBUTE_ID)
Now, we need a means of reading our attribute and turning the light on or off based on this attribute. Because this callback is made after the attribute has changed, we can read this attribute to get its state. To do this we will need to look through the documentation of EmberZNet. If you go to Docs.silabs.com, you can find the Ember Application framework API
On the left, if you click Ember Application Framework API Reference it will show you the sections of the top-level API. At this point we just want the General Application Framework Interface. When you get this page, you will find the Attribute storage section listed at the very top. We are looking for some function to read an attribute, a little searching will find the function emberAfReadServerAttribute, a function that is doing exactly what we want, reading a server attribute, so copy that whole definition and paste it into your code.
EmberAfStatus emberAfReadServerAttribute (uint8_t endpoint, EmberAfClusterId cluster, EmberAfAttributeId attributeID, uint8_t *dataPtr, uint8_t readLength)
Because the function returns an EmberAfStatus, create one to catch the return value. We should only change our LED if the read was successful. You can return to the API guide and look up EmberAfStatus, you will find this is EMBER_ZCL_STATUS_SUCCESS.
if(readStatus == EMBER_ZCL_STATUS_SUCCESS)
Finally we need to make sure we properly read our attribute and change our LED, so go back to emberAfReadServerAttribute function call and let’s set it up properly. We know that our endpoint is the endpoint passed to our callback, so make sure this matches. The cluster ID is that for the on/off cluster. If you look at cluster-id.h, you will see that it is enumerated as ZCL_ON_OFF_CLUSTER_ID, so put that as the 2nd variable in the call. The attribute ID is again ZCL_ON_OFF_ATTRIBUTE_ID, so use that. Finally, we need something to catch the value of the data. I created a boolean type named onOff to catch this, the last two variables of the signature are a pointer to this variable and the size of that variable. In the end our full call looks something like this:
boolean onOff; EmberAfStatus readStatus; readStatus = emberAfReadServerAttribute(endpoint, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, &onOff, sizeof(onOff));
Now we can just use an if-else called against onOff to turn on or turn off our LED. The checking EMLIB again, we see that the function calls for this are:
Make the appropriate calls to the LEDs you want on and off within your code block, save and recompile your code. You can then reflash it to your “light” Thunderboard and use the switch to toggle it from the CLI. If you are able to turn the lights on and off, it’s time to move on to our switch to get it to toggle from the buttons.