This intent of this guide is to describe the step by step process of creating an EEL from scratch. This EEL will control the MikroElektronika Relay Click board.
- Node.js 8.x.x (An older version may work)
- npm 5.x.x (An older version may work)
Using your tool of choice, clone this repository to your local machine:
git clone git@github.com:atmosphereiot/eel-builder.git
Navigate to the newly created eel-builder directory and run npm install
to install dependencies that the eel-builder needs.
Let's create a new directory to hold all of our EEL source and metadata. To do this, navigate to the eel-builder directory and run node eelbuilder.js --new --name="relayclick" --dir="../"
You should see that a new directory, relayclick_EEL
was created one level up in the same directory as the eel-builder directory.
NOTE: The --dir
argument can be anything you like. It's the directory in wich the new source directory will be created. You could put it in C:\Users\me\Documents
or /home/me/Documents
, for example.
Navigate to your relayclick_EEL directory. Open the file metadata.json. This file contains all of the high level metadata about your EEL that the studio needs to properly display and use it.
Change the manufacturer name to MikroElektronika.
Write a short description of your EEL here. Something like "Relay Click evaluation board" would suffice. You could leave this blank if you wanted.
This EEL will require the GPIO driver, so add gpio
to the list. You should have something like this:
"requires": [
"embedded",
"gpio"
],
This field allows the studio to filter EELs that are not compatible with certain platforms.
In the Elements object, set the name to "RelayClick". This is the name that would be displayed in the Element Library browser in the "Element" column.
In the Elements object, set the element type to "EmbeddedRelayClick". This type is used for the language object and is generally how the Studio refers to this element.
For the icon, just use the generic EmbeddedRelay.svg
.
Change myability
to setup and add a field hidden
set to true. Every element must have a setup ability, and it should be hidden. It should look like this:
"abilities": [
{
"name": "setup",
"hidden": true,
"triggers": []
}
],
Add a new ability called setRelay1On
with a trigger relay1SetOn
. Once you've done this, add a third ability called setRelay1Off
with a trigger relay1SetOff
. Finally, add an ability toggleRelay1
with a trigger relay1Toggled
. Your final abilities list should look like this:
You must set the default ability and trigger selected when the element is initially created. Set defaultAbility
to setRelay1On
and defaultTrigger
to relay1SetOn
.
Delete the default myproperty
property. Thinking about the relay click board, what does it need in order to function? The EEL needs to know the GPIO Driver Instance and the GPIO Pin connected to relay 1, so those are our two properties.
Your properties list should look like this:
"properties": [
{
"name": "gpioDriverInstance",
"input": "driverInstance",
"driverType": "gpio"
},
{
"name": "relay1GpioPin",
"input": "number",
"value": 0
}
],
The last step in the metedata is the language object. This tells the studio how to print the element name, ability names, and trigger names. It should look something like this:
"language": {
"en-US": {
"EmbeddedRelayClick": "Relay Click",
"setRelay1On": "Set Relay 1 On",
"relay1SetOn": "Relay 1 Set On",
"setRelay1Off": "Set Relay 1 Off",
"relay1SetOff": "Relay 1 Set Off",
"toggleRelay1": "Toggle Relay 1",
"relay1Toggled": "Relay 1 Toggled"
}
}
Your final metadata.json file should look like this:
{
"libName": "relayclick",
"manufacturer": "MikroElektronika",
"description": "Dual Relay Evaluation Board",
"version": "",
"eelVersion": "3",
"shoppingCarLinks": {},
"requires": [
"embedded",
"gpio"
],
"elements": [
{
"name": "RelayClick",
"type": "EmbeddedRelayClick",
"icon": "EmbeddedFuncton.svg",
"defaultAbility": "setRelay1On",
"defaultTrigger": "relay1SetOn",
"hidden": false,
"abilities": [
{
"name": "setup",
"hidden": true,
"triggers": []
},
{
"name": "setRelay1On",
"triggers": ["relay1SetOn"]
},
{
"name": "setRelay1Off",
"triggers": ["relay1SetOff"]
},
{
"name": "toggleRelay1",
"triggers": ["relay1Toggled"]
}
],
"properties": [
{
"name": "gpioDriverInstance",
"input": "driverInstance",
"driverType": "gpio"
},
{
"name": "relay1GpioPin",
"input": "number",
"value": 0
}
],
"triggers": [],
"variables": [],
"language": {
"en-US": {
"EmbeddedRelayClick": "Relay Click",
"setRelay1On": "Set Relay 1 On",
"relay1SetOn": "Relay 1 Set On",
"setRelay1Off": "Set Relay 1 Off",
"relay1SetOff": "Relay 1 Set Off",
"toggleRelay1": "Toggle Relay 1",
"relay1Toggled": "Relay 1 Toggled"
}
}
}
]
}
We can use the tool to generate stub .c files that we can fill in later for all of its abilities. To do this, navigate to the eel-builder directory and run
node eelbuilder.js --generate --dir="../relayclick_EEL" --dest="../"
You should see the following output:
Ability dir does not exist, creating it.
Unable to read ability source ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setup.c
Generating default stub ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setup.c
Unable to read ability source ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setRelay1On.c
Generating default stub ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setRelay1On.c
Unable to read ability source ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setRelay1Off.c
Generating default stub ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/setRelay1Off.c
Unable to read ability source ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/toggleRelay1.c
Generating default stub ../relayclick_EEL/elements/EmbeddedRelayClick/abilities/toggleRelay1.c
Writing EEL to ..\relayclick.eel
This step is actually writing the C driver for the EEL. This will be located in relayclick_EEL/files/common
.
Create a new file: relayclick_EEL/files/common/headers/relayclick.h
Create a new structure to hold your configuration data (properties). You should have something like this, at this point:
#ifndef ATMO_RELAYCLICK_H_
#define ATMO_RELAYCLICK_H_
#include "../app_src/atmosphere_platform.h"
typedef struct {
ATMO_DriverInstanceHandle_t gpioDriverInstance;
ATMO_GPIO_Device_Pin_t relay1Pin;
} ATMO_RelayClick_Config_t;
#endif
Note that these two struct items refer to the properties you defined in your metadata. Also note the #include "../atmo/core.h"
. This should be done in every EEL, as it will import any datatypes you may need.
The next step is to create the function prototypes that we will need. You should end up with something like this:
#ifndef ATMO_RELAYCLICK_H_
#define ATMO_RELAYCLICK_H_
#include "../app_src/atmosphere_platform.h"
typedef struct {
ATMO_DriverInstanceHandle_t gpioDriverInstance;
ATMO_GPIO_Device_Pin_t relay1Pin;
} ATMO_RelayClick_Config_t;
bool ATMO_RelayClick_Init(ATMO_RelayClick_Config_t *config);
bool ATMO_RelayClick_SetRelay1State(bool isOn);
bool ATMO_RelayClick_ToggleRelay1();
#endif
Create a new file: relayclick_EEL/files/common/objects/relayclick.c
The first step is to include the header: #include "relayclick.h"
NOTE Although the header and source are currently in different directories, they will be in the same directory when Atmosphere Studio generates the embedded source for your project.
The next step is to implement the initialization function:
#include "relayclick.h"
// Private internal storage of driver configuration
static ATMO_RelayClick_Config_t _ATMO_RelayClick_PrivConfig;
bool ATMO_RelayClick_Init(ATMO_RelayClick_Config_t *config)
{
// Copy the configuration structure and store internally
memcpy(&_ATMO_RelayClick_PrivConfig, config, sizeof(ATMO_RelayClick_Config_t));
// Set the default configuration and state of Relay1
ATMO_GPIO_Config_t gpioConfig;
gpioConfig.pinMode = ATMO_GPIO_PinMode_Output_PushPull;
gpioConfig.initialState = ATMO_GPIO_PinState_Low;
return ATMO_GPIO_SetPinConfiguration(config->gpioDriverInstance, config->relay1Pin, &gpioConfig) == ATMO_GPIO_Status_Success;
}
The documentation for the GPIO driver can be found here. The include path for any driver will be "../{drivername}/{drivername}.h"
.
The final step is to implement the two Relay1 control functions:
bool ATMO_RelayClick_SetRelay1State(bool isOn)
{
ATMO_GPIO_PinState_t desiredPinState = isOn ? ATMO_GPIO_PinState_High : ATMO_GPIO_PinState_Low;
return (ATMO_GPIO_SetPinState(_ATMO_RelayClick_PrivConfig.gpioDriverInstance, _ATMO_RelayClick_PrivConfig.relay1Pin, desiredPinState) == ATMO_GPIO_Status_Success);
}
bool ATMO_Relayclick_ToggleRelay1()
{
return (ATMO_GPIO_Toggle(_ATMO_RelayClick_PrivConfig.gpioDriverInstance, _ATMO_RelayClick_PrivConfig.relay1Pin) == ATMO_GPIO_Status_Success);
}
In the end, you should end up with a final file looking like this:
#include "relayclick.h"
// Private internal storage of driver configuration
static ATMO_RelayClick_Config_t _ATMO_RelayClick_PrivConfig;
bool ATMO_RelayClick_Init(ATMO_RelayClick_Config_t *config)
{
// Copy the configuration structure and store internally
memcpy(&_ATMO_RelayClick_PrivConfig, config, sizeof(ATMO_RelayClick_Config_t));
// Set the default configuration and state of Relay1
ATMO_GPIO_Config_t gpioConfig;
gpioConfig.pinMode = ATMO_GPIO_PinMode_Output_PushPull;
gpioConfig.initialState = ATMO_GPIO_PinState_Low;
return ATMO_GPIO_SetPinConfiguration(config->gpioDriverInstance, config->relay1Pin, &gpioConfig) == ATMO_GPIO_Status_Success;
}
bool ATMO_RelayClick_SetRelay1State(bool isOn)
{
ATMO_GPIO_PinState_t desiredPinState = isOn ? ATMO_GPIO_PinState_High : ATMO_GPIO_PinState_Low;
return (ATMO_GPIO_SetPinState(_ATMO_RelayClick_PrivConfig.gpioDriverInstance, _ATMO_RelayClick_PrivConfig.relay1Pin, desiredPinState) == ATMO_GPIO_Status_Success);
}
bool ATMO_RelayClick_ToggleRelay1()
{
return (ATMO_GPIO_Toggle(_ATMO_RelayClick_PrivConfig.gpioDriverInstance, _ATMO_RelayClick_PrivConfig.relay1Pin) == ATMO_GPIO_Status_Success);
}
The next step is to write the source for the abilities. This is the code that appears in the code view in Studio. This code will be using the driver we just wrote in Step 6.
Navigate to relayclick_EEL/elements/EmbeddedRelayClick/abilities/
and open setup.c
.
Using the ATMO_PROPERTY macro, we can retrieve the properties from the element toolbox and set them in the configuration struct we created:
ATMO_RelayClick_Config_t config;
config.gpioDriverInstance = ATMO_PROPERTY(undefined, gpioDriverInstance);
config.relay1Pin = ATMO_PROPERTY(undefined, relay1GpioPin);
return ATMO_RelayClick_Init(&config) ? ATMO_Status_Success : ATMO_Status_Fail;
NOTE: The ATMO_PROPERTY macro accepts two arguments: element name and property name. Using undefined as the element name tells the studio to automatically fill in the name of the element instance before the code is compiled. For example, if you create Relay Click element and name it "NicksRelayClick", this code will end up looking as follows before compilation:
ATMO_RelayClick_Config_t config;
config.gpioDriverInstance = ATMO_PROPERTY(NicksRelayClick, gpioDriverInstance);
config.relay1Pin = ATMO_PROPERTY(NicksRelayClick, relay1GpioPin);
return ATMO_RelayClick_Init(&config) ? ATMO_Status_Success : ATMO_Status_Fail;
The next step is to fill in the other three abilities. Your setRelay1Off.c
should look like this:
return ATMO_RelayClick_SetRelay1State(false) ? ATMO_Status_Success : ATMO_Status_Fail;
Your setRelay1On.c
should look like this:
return ATMO_RelayClick_SetRelay1State(true) ? ATMO_Status_Success : ATMO_Status_Fail;
Your toggleRelay1.c
should look like this:
return ATMO_RelayClick_ToggleRelay1() ? ATMO_Status_Success : ATMO_Status_Fail;
Navigate to the eel-builder directory and run: node eelbuilder.js --generate --dir="../relayclick_EEL" --dest="../"
Congratulations, you've created your first EEL. You can see a more complex version of this EEL in the examples directory, here.