Page tree
Skip to end of metadata
Go to start of metadata

Introduction

DK IoT Studio is a rapid prototyping environment that allows developers to quickly implement IoT applications without having to write a single line of code. IoT devices can be programmed to interface with a variety of sensors and send/receive data to/from a mobile device or the cloud. There are several embedded platforms to choose from and a wide array of sensors they can interface with. The complete list of sensors (available in atmosphere's element documentation) continues to grow, but situations will inevitably arise where a developer wishes to utilize a sensor not currently supported in IoT Studio. Luckily, users have the option to develop their own Embedded Element Libraries (EELs) and import them to their project.

Please note that at the time of writing, "sensor elements" in IoT Studio refer to EELs for both sensors and actuators. For example, in the list of sensor elements you will find the MikroE 8x8 LED Click and the MikroE Relay Click. So, keep in mind that every reference to "sensors" or "sensor elements" in this article does not preclude actuators from its context. Indeed, you can develop EELs for actuators as my college demonstrates in his article Creating an Embedded Element Library From Scratch (with AVR-IoT + Trinamic Example).

Often, users may wish to develop a sensor element very similar to one already available in IoT Studio or they may wish to modify/augment the functionality of an existing sensor element. In either case, it would be very helpful to have access to the EEL source code for these elements so it may be easily forked by the user. Consider for example the Teseo-LIV3F mikroBUS Add-on Board I designed a while back. After having the boards made and assembling them, I wanted to utilize one of them in an IoT Studio project. While the Teseo-LIV3F GPS module is not currently supported in IoT Studio, there is an element for the very similar Nano GPS Click board. Using the procedure outlined below, I was able to use the Nano GPS Click element’s source code as a starting point in developing an EEL for my custom board. Of course, the same procedure will work for any existing sensor element.

Prerequisites

The following procedure assumes that the reader is familiar with Atmosphere's EEL Builder tool. Not only will we be using this tool to build up the EEL source code, but the tool's documentation is essentially an explanation of exactly what EELs are and how they work with IoT Studio to provide support for a particular sensor. The GitHub repository also contains several example EELs which may prove helpful.

Procedure

The EEL Builder tool uses the EEL source code to generate an EEL file (a file with the extension ".eel"). This EEL file can be imported into any IoT Studio project, and if it is, it will be embedded in the Atmosphere project file (a file with the extension ".atmo"). The steps presented below essentially reverse this process, using the project file along with the project's source code to obtain the EEL source code for a particular sensor element utilized in the project. Using the source code is not strictly necessary as all the required information is embedded in the project file, but I find that using the source code somewhat simplifies the process.

Take note right away in the difference between “EEL source code” and the “project source code”. The EEL source code includes the files used to generate the EEL file (see Figure 9 as an example) while the project source code is the code that will be compiled and programmed to the embedded device (see Step 2 in Figure 1).

1. Download the sensor element’s source code

By downloading a project’s source code, we are also downloading the source code of any sensor elements used within the project. Start by creating a new project and using the Add Element button in the Element Toolbox to import the element you want to replicate (if it’s not already in the Element Toolbox). Place that element on the canvas and save/compile the project as shown in Figure 1. To keep the source code as simple as possible, you shouldn’t add any other elements to the canvas before compiling. Once the project successfully compiles, click the Download Source button in the Embedded tab to download the project source code in a .zip file. Extract this file to a location of your choosing. We will need it later for steps 6 and 7.

Figure 1: Save and compile the IoT Studio project and then download the source code

The embedded platform you choose when creating a new project does not matter. All we care about in the source code are the sensor element’s driver files. The files related to the specific platform we chose are not needed for this process and will be ignored. However, you may find (like I did) that your choice of platform is somewhat limited as not all platforms support all sensor elements. For example, the Nano GPS Click element imported in Figure 1 uses the UART peripheral to communicate with the embedded device. Therefore, if the platform that was selected when creating the project does not support UART, the Nano GPS Click element will not appear in the import dialog. For instance, when I created a Sensor Tile project (which does not support UART) and tried to import the Nano GPS Click element, it was not listed. So, you must choose a platform that supports all the features required by the sensor element.

2. Export the project and extract the EEL file

Return to the Studio Projects screen. Click the Export Project icon shown below in Figure 2 to save the project's ".atmo" file. This is a plaintext file that can be opened with whichever text editor you prefer. Among other things, it includes the contents of the .eel files of any added sensor elements.

Figure 2: Export project

Open the .atmo file and search for the string "libName" to find where the EEL data begins. If there are multiple elements in your project, simply repeat your search until you find the name of the element you intend on replicating. In my case, I found the beginning of the Nano GPS Click EEL data right away (Figure 3). The open curly brace immediately preceding the libName key is the first character in the .eel file. Copy everything from this open curly brace to its corresponding closing brace and paste it to a new file (Listing 1). As you can see in Listing 1, I've also decreased the indentation by a few steps to ensure everything lines up properly.

In the next step we will use this file to create the metadata.json file, but if for whatever reason all you need is the EEL file and not the EEL source code, you can stop here. Simply save this file as <.libname>.eel and import it to another IoT Studio project. 



Listing 1: nanoGPSClick EEL data
{
  "libName": "nanoGPSClick",
  "manufacturer": "MikroElektronika",
  "description": "ORG1411 GPS RF mikroBUS™ Click™ Platform Evaluation Expansion Board",
  "type": "GPS",
  "icon": "",
  "image": "",
  "version": "",
  "eelVersion": "3",
  "shoppingCartLinks": {
	"digikey": {
	  "links": {
		"info": "https://www.digikey.com/short/j21859"
	  },
	  "cartData": {
		"part": "1471-1441-ND",
		"partid": "5436803",
		"source": "dkstudio",
		"qty": "1"
	  }
	}
  },
  "requires": [
	"embedded",
	"uart",
	"gpio"
  ],
  "elements": [
	{
	  "name": "GpsClick",
	  "type": "EmbeddedGpsClick",
	  "icon": "EmbeddedGpsClick.svg",
	  "defaultAbility": "setup",
	  "defaultTrigger": "locationReceived",
	  "hidden": false,
	  "helpPageHref": "https://developer.atmosphereiot.com/documents/elements/mikroelektronikagpsclickelement.html",
	  "abilities": [
		{
		  "name": "setup",
		  "hidden": true,
		  "code": "ATMO_GPS_NANO_Config_t config;\nconfig.reportFreq = ATMO_PROPERTY(undefined, reportFrequency);\nconfig.powerPin = ATMO_PROPERTY(undefined, powerPin);\nconfig.wupPin = ATMO_PROPERTY(undefined, wupPin);\nconfig.gpioDriverInstance = ATMO_PROPERTY(undefined, gpioDriverInstance);\nconfig.uartDriverInstance = ATMO_PROPERTY(undefined, uartDriverInstance);\nconfig.rstPin = ATMO_PROPERTY(undefined, rstPin);\n\nif(ATMO_GPS_NANO_Init(&config) != ATMO_GPS_NANO_Status_Success)\n{\n    return ATMO_Status_Fail;\n}\n\nATMO_GPS_NANO_RegisterLocRxAbilityHandle(ATMO_ABILITY(undefined, locationReceived));\nATMO_GPS_NANO_RegisterLatRxAbilityHandle(ATMO_ABILITY(undefined, latitudeReceived));\nATMO_GPS_NANO_RegisterLongRxAbilityHandle(ATMO_ABILITY(undefined, longitudeReceived));\n\nreturn ATMO_Status_Success;"
		},
		{
		  "name": "locationReceived",
		  "triggers": [
			"locationReceived"
		  ],
		  "hidden": true,
		  "code": "ATMO_CreateValueCopy(out, in);\nreturn ATMO_Status_Success;"
		},
		{
		  "name": "latitudeReceived",
		  "triggers": [
			"latitudeReceived"
		  ],
		  "hidden": true,
		  "code": "ATMO_CreateValueCopy(out, in);\nreturn ATMO_Status_Success;"
		},
		{
		  "name": "longitudeReceived",
		  "triggers": [
			"longitudeReceived"
		  ],
		  "hidden": true,
		  "code": "ATMO_CreateValueCopy(out, in);\nreturn ATMO_Status_Success;"
		}
	  ],
	  "properties": [
		{
		  "name": "uartDriverInstance",
		  "input": "driverInstance",
		  "driverType": "uart"
		},
		{
		  "name": "gpioDriverInstance",
		  "input": "driverInstance",
		  "driverType": "gpio"
		},
		{
		  "name": "powerPin",
		  "input": "number",
		  "value": 0
		},
		{
		  "name": "wupPin",
		  "input": "number",
		  "value": 0
		},
		{
		  "name": "rstPin",
		  "input": "number",
		  "value": 0
		},
		{
		  "name": "reportFrequency",
		  "input": "number",
		  "value": 1
		}
	  ],
	  "triggers": [],
	  "variables": [],
	  "language": {
		"en-US": {
		  "EmbeddedGpsClick": "Nano GPS Click",
		  "uartDriverInstance": "UART Driver Instance",
		  "reportFrequency": "Location Report Frequency(Hz)",
		  "positionReceived": "Position received",
		  "gpioDriverInstance": "GPIO Driver Instance",
		  "powerPin": "Power Pin",
		  "wupPin": "WUP Pin",
		  "rstPin": "Reset Pin",
		  "locationReceived": "Location Received",
		  "latitudeReceived": "Latitude Received",
		  "longitudeReceived": "Longitude Received"
		}
	  }
	}
  ],
  "files": {
	"common": {
	  "headers": {
		"nanogps.h": "#ifndef __ATMO_GPS_NANO_H_\n#define __ATMO_GPS_NANO_H_\n\n#include \"../uart/uart.h\"\n#include \"../gpio/gpio.h\"\n\ntypedef enum {\n    ATMO_GPS_NANO_Status_Success              = 0x00u,  // Common - Operation was successful\n    ATMO_GPS_NANO_Status_Fail                 = 0x01u,  // Common - Operation failed\n    ATMO_GPS_NANO_Status_Initialized          = 0x02u,  // Common - Peripheral already initialized\n    ATMO_GPS_NANO_Status_Invalid              = 0x03u,  // Common - Invalid operation or result\n    ATMO_GPS_NANO_Status_NotSupported         = 0x04u,  // Common - Feature not supported by platform\n} ATMO_GPS_NANO_Status_t;\n\ntypedef struct {\n    unsigned int reportFreq;\n    ATMO_GPIO_Device_Pin_t powerPin;\n    ATMO_GPIO_Device_Pin_t wupPin;\n    ATMO_GPIO_Device_Pin_t rstPin;\n    ATMO_DriverInstanceHandle_t gpioDriverInstance;\n    ATMO_DriverInstanceHandle_t uartDriverInstance;\n} ATMO_GPS_NANO_Config_t;\n\ntypedef struct\n{\n\tuint32_t hrsUtc;\n\tuint32_t minutesUtc;\n\tuint32_t secondsUtc;\n\tfloat latitude;\n\tfloat longitude;\n\tATMO_BOOL_t posFix;\n}ATMO_GPS_NANO_LocData_t;\n\n/**\n * Initialize GPS_NANO Driver\n *\n * @param[in] config - Device configuration (optional)\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_Init(ATMO_GPS_NANO_Config_t *config);\n\n/**\n * Set basic device configuration\n *\n * @param[in] config\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_SetConfiguration(const ATMO_GPS_NANO_Config_t *config);\n\n/**\n * Get Current Location Info\n * \n * @param[out] locData\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_GetLocation(ATMO_GPS_NANO_LocData_t *locData);\n\n/**\n * Register ability handler for location data received\n * \n * @param[in] abilityHandle\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLocRxAbilityHandle(unsigned int abilityHandle);\n\n/**\n * Register ability handler for latitude data received\n * \n * @param[in] abilityHandle\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLatRxAbilityHandle(unsigned int abilityHandle);\n\n/**\n * Register ability handler for longitude data received\n * \n * @param[in] abilityHandle\n */\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLongRxAbilityHandle(unsigned int abilityHandle);\n\n\n\n#endif\n"
	  },
	  "objects": {
		"nanogps.c": "\n#include \"../app_src/atmosphere_platform.h\"\n#include \"nanogps.h\"\n\ntypedef struct {\n    ATMO_GPS_NANO_Config_t config;\n    unsigned int locRxAbilityHandle;\n    unsigned int latRxAbilityHandle;\n    unsigned int longRxAbilityHandle;\n    ATMO_BOOL_t latRxAbilityHandleSet;\n    ATMO_BOOL_t longRxAbilityHandleSet;\n    ATMO_BOOL_t locRxAbilityHandleSet;\n    ATMO_BOOL_t configured;\n} ATMO_GPS_NANO_Priv_Config;\n\nstatic ATMO_GPS_NANO_Priv_Config _ATMO_GPS_NANO_config;\n\nstatic ATMO_BOOL_t lastLocDataReceived = false;\nstatic ATMO_GPS_NANO_LocData_t lastLocData;\n\nstatic void ATMO_GPS_NANO_Pulse()\n{\n    if(!_ATMO_GPS_NANO_config.configured)\n    {\n        return;\n    }\n\tATMO_GPIO_SetPinState(_ATMO_GPS_NANO_config.config.gpioDriverInstance, _ATMO_GPS_NANO_config.config.powerPin, ATMO_GPIO_PinState_Low);\n\tATMO_PLATFORM_DelayMilliseconds(200);\n\tATMO_GPIO_SetPinState(_ATMO_GPS_NANO_config.config.gpioDriverInstance, _ATMO_GPS_NANO_config.config.powerPin, ATMO_GPIO_PinState_High);\n\tATMO_PLATFORM_DelayMilliseconds(3000);\n\tATMO_GPIO_SetPinState(_ATMO_GPS_NANO_config.config.gpioDriverInstance, _ATMO_GPS_NANO_config.config.powerPin, ATMO_GPIO_PinState_Low);\n\tATMO_PLATFORM_DelayMilliseconds(200);\n}\n\nvoid ATMO_GPS_NANO_FactoryReset()\n{\n    if(!_ATMO_GPS_NANO_config.configured)\n    {\n        return;\n    }\n\tuint32_t dummy = 0;\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, \"$PSRF101,0,0,0,000,0,0,12,8*1C\\r\\n\", strlen(\"$PSRF101,0,0,0,000,0,0,12,8*1C\\r\\n\"), &dummy, 100);\n}\n\nvoid ATMO_GPS_NANO_DisableOutput()\n{\n    if(!_ATMO_GPS_NANO_config.configured)\n    {\n        return;\n    }\n\tuint32_t dummy = 0;\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, \"$PSRF103,02,00,00,01*26\\r\\n\", strlen(\"$PSRF103,02,00,00,01*26\\r\\n\"), &dummy, 100);\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, \"$PSRF103,03,00,00,01*27\\r\\n\", strlen(\"$PSRF103,03,00,00,01*27\\r\\n\"), &dummy, 100);\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, \"$PSRF103,04,00,00,01*20\\r\\n\", strlen(\"$PSRF103,04,00,00,01*20\\r\\n\"), &dummy, 100);\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, \"$PSRF103,00,00,00,01*24\\r\\n\", strlen(\"$PSRF103,00,00,00,01*24\\r\\n\"), &dummy, 100);\n}\n\nuint8_t ATMO_GPS_NANO_NMEAChecksum(const char *str)\n{\n\twhile(*str == '$')\n\t{\n\t\tstr++;\n\t}\n\n\tuint8_t checksum = 0;\n\n\twhile(*str != '*')\n\t{\n\t\tchecksum ^= *str;\n\t\tstr++;\n\t}\n\n\treturn checksum;\n}\n\nvoid ATMO_GPS_NANO_SetReportRate(int reportRateHz)\n{\n\tuint32_t dummy = 0;\n\tchar cmdStr[128];\n\tsprintf(cmdStr, \"$PSRF103,00,00,%d,01*\", reportRateHz);\n\tuint8_t checksum = ATMO_GPS_NANO_NMEAChecksum(cmdStr);\n\tsprintf(cmdStr + strlen(cmdStr), \"%02X\\r\\n\", checksum);\n\tATMO_UART_WriteBlocking(_ATMO_GPS_NANO_config.config.uartDriverInstance, cmdStr, strlen(cmdStr), &dummy, 100);\n}\n\nconst char* ATMO_GPS_NANO_GetNextField(const char* start, uint32_t *len)\n{\n\twhile(*start == ',')\n\t{\n\t\tstart++;\n\t}\n\n\tconst char *currToken = strchr(start, ',');\n\n\tif(currToken == NULL)\n\t{\n\t\t*len = 0;\n\t\treturn NULL;\n\t}\n\n\twhile(*currToken == ',')\n\t{\n\t\tcurrToken++;\n\t}\n\n\tconst char *next = strchr(currToken + 1, ',');\n\n\tif(next == NULL)\n\t{\n\t\t*len = (&currToken[strlen(currToken) - 1] - currToken);\n\t}\n\telse\n\t{\n\t\t*len = (uint32_t)(next - currToken);\n\t}\n\n\treturn currToken;\n}\n\nATMO_BOOL_t ATMO_GPS_NANO_ParseMessage(const char* str, ATMO_GPS_NANO_LocData_t *msg)\n{\n\tmsg->posFix = false;\n\n\tconst char *token = strstr(str,\"$GPGGA\");\n\tif(token == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tuint32_t fieldLength = 0;\n\n\t// UTC time\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 6))\n\t{\n\t\treturn false;\n\t}\n\n\tchar hrStr[3] = {0};\n\tchar minStr[3] = {0};\n\tchar secStr[3] = {0};\n\tmemcpy(hrStr, token, 2);\n\ttoken += 2;\n\tmemcpy(minStr, token, 2);\n\ttoken += 2;\n\tmemcpy(secStr, token, 2);\n\tmsg->hrsUtc = strtoul(hrStr, NULL, 10);\n\tmsg->minutesUtc = strtoul(minStr, NULL,  10);\n\tmsg->secondsUtc = strtoul(secStr, NULL, 10);\n\n\n\t// Latitude\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 4))\n\t{\n\t\treturn false;\n\t}\n\n\tchar degStr[3] = {0};\n\tmemcpy(degStr, token, 2);\n\ttoken += 2;\n\tmsg->latitude = strtoul(degStr, NULL, 10) + (strtof(token, NULL) / 60);\n\n\n\t// N/S indicator\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 1))\n\t{\n\t\treturn false;\n\t}\n\n\tif(*token == 'S')\n\t{\n\t\tmsg->latitude *= -1;\n\t}\n\n\t// Longitude\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 4))\n\t{\n\t\treturn false;\n\t}\n\n\tmemcpy(degStr, token, 3);\n\ttoken += 3;\n\tmsg->longitude = strtoul(degStr, NULL, 10) + (strtof(token, NULL) / 60);\n\n\t// E/W indicator\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 1))\n\t{\n\t\treturn false;\n\t}\n\n\tif(*token == 'W')\n\t{\n\t\tmsg->longitude *= -1;\n\t}\n\n\t// Position fix\n\ttoken = ATMO_GPS_NANO_GetNextField(token, &fieldLength);\n\n\tif(!token || (fieldLength < 1))\n\t{\n\t\treturn false;\n\t}\n\n\tif(*token == '0')\n\t{\n\t\tmsg->posFix = false;\n\t}\n\telse if(*token == '1' || *token == '2' || *token == '6')\n\t{\n\t\tmsg->posFix = true;\n\t}\n\telse\n\t{\n\t\tmsg->posFix = false;\n\t}\n\n\treturn true;\n}\n\nvoid ATMO_GPS_NANO_RxCb(void *data)\n{\n\tchar str[256];\n\tATMO_GetString((ATMO_Value_t *)data, str, 256);\n\n\tATMO_PLATFORM_DebugPrint(\"Str: %s\\r\\n\", str);\n\n    // Try to get location info\n    ATMO_GPS_NANO_LocData_t locData;\n    if(ATMO_GPS_NANO_ParseMessage(str, &locData))\n    {\n        lastLocDataReceived = true;\n        memcpy(&lastLocData, &locData, sizeof(locData));\n\n        if(_ATMO_GPS_NANO_config.locRxAbilityHandleSet)\n        {\n            ATMO_Value_t val;\n            ATMO_CreateValueBinary(&val, &locData, sizeof(locData));\n            ATMO_AddAbilityExecute(_ATMO_GPS_NANO_config.locRxAbilityHandle, &val);\n        }\n        if(_ATMO_GPS_NANO_config.longRxAbilityHandleSet)\n        {\n            ATMO_Value_t val;\n            ATMO_CreateValueFloat(&val, locData.longitude);\n            ATMO_AddAbilityExecute(_ATMO_GPS_NANO_config.longRxAbilityHandle, &val);\n        }\n        if(_ATMO_GPS_NANO_config.latRxAbilityHandleSet)\n        {\n            ATMO_Value_t val;\n            ATMO_CreateValueFloat(&val, locData.latitude);\n            ATMO_AddAbilityExecute(_ATMO_GPS_NANO_config.latRxAbilityHandle, &val);\n        }\n    }\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_Init(ATMO_GPS_NANO_Config_t *config)\n{\n\tmemset(&_ATMO_GPS_NANO_config, 0, sizeof(_ATMO_GPS_NANO_config));\n\n    // Did the user supply a configuration?\n    if( config )\n    {\n        ATMO_GPS_NANO_SetConfiguration(config);\n    }\n    else\n    {\n    \t_ATMO_GPS_NANO_config.configured = false;\n    }\n\n    return ATMO_GPS_NANO_Status_Success;\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_SetConfiguration(const ATMO_GPS_NANO_Config_t *config)\n{\n    if( config == NULL )\n    {\n        return ATMO_GPS_NANO_Status_Fail;\n    }\n\n    memcpy( &_ATMO_GPS_NANO_config.config, config, sizeof(ATMO_GPS_NANO_Config_t) );\n    _ATMO_GPS_NANO_config.configured = true;\n\n    // Initialize UART\n    ATMO_UART_Init(config->uartDriverInstance);\n\n\tATMO_UART_Peripheral_t uart;\n\tATMO_UART_GetDefaultConfig(&uart);\n\tuart.baudRate = ATMO_UART_BaudRate_4800;\n\tuart.rxBuffer = true;\n\tuart.isBinaryData = false;\n\tstrcpy(uart.splitRegex, \"\\\\$GPGGA.*\\r\\n$\");\n\tATMO_UART_SetConfiguration(config->uartDriverInstance, &uart);\n\tATMO_UART_RegisterRxCbFunc(config->uartDriverInstance, ATMO_GPS_NANO_RxCb);\n\n    // Reset GPS\n\tATMO_GPIO_Config_t gpioConfig;\n\tgpioConfig.initialState = ATMO_GPIO_PinState_High;\n\tgpioConfig.pinMode = ATMO_GPIO_PinMode_Output_PushPull;\n\tATMO_GPIO_SetPinConfiguration(config->gpioDriverInstance, config->rstPin, &gpioConfig);\n\n\t// At least 100ms\n\tATMO_PLATFORM_DelayMilliseconds(300);\n\tATMO_GPIO_SetPinState(config->gpioDriverInstance, config->rstPin, ATMO_GPIO_PinState_Low);\n\n\tgpioConfig.initialState = ATMO_GPIO_PinState_Low;\n\tATMO_GPIO_SetPinConfiguration(config->gpioDriverInstance, config->powerPin, &gpioConfig);\n\n\tgpioConfig.pinMode = ATMO_GPIO_PinMode_Input_PullUp;\n\tATMO_GPIO_SetPinConfiguration(config->gpioDriverInstance, config->wupPin, &gpioConfig);\n\n    // Reset GPS\n    ATMO_GPS_NANO_Pulse();\n\tATMO_PLATFORM_DelayMilliseconds(100);\n\n\twhile(ATMO_GPIO_Read(config->gpioDriverInstance, config->wupPin) != ATMO_GPIO_PinState_High)\n\t{\n\t\tATMO_GPS_NANO_Pulse();\n\t}\n\n    ATMO_GPS_NANO_DisableOutput();\n    ATMO_GPS_NANO_SetReportRate(config->reportFreq);\n\n    ATMO_PLATFORM_DebugPrint(\"GPS Initialized\\r\\n\");\n\n    return ATMO_GPS_NANO_Status_Success;\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLocRxAbilityHandle(unsigned int abilityHandle)\n{\n    _ATMO_GPS_NANO_config.locRxAbilityHandleSet = true;\n    _ATMO_GPS_NANO_config.locRxAbilityHandle = abilityHandle;\n    return ATMO_GPS_NANO_Status_Success;\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLatRxAbilityHandle(unsigned int abilityHandle)\n{\n    _ATMO_GPS_NANO_config.latRxAbilityHandleSet = true;\n    _ATMO_GPS_NANO_config.latRxAbilityHandle = abilityHandle;\n    return ATMO_GPS_NANO_Status_Success;\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_RegisterLongRxAbilityHandle(unsigned int abilityHandle)\n{\n    _ATMO_GPS_NANO_config.longRxAbilityHandleSet = true;\n    _ATMO_GPS_NANO_config.longRxAbilityHandle = abilityHandle;\n    return ATMO_GPS_NANO_Status_Success;\n}\n\nATMO_GPS_NANO_Status_t ATMO_GPS_NANO_GetLocation(ATMO_GPS_NANO_LocData_t *locData)\n{\n    if(!lastLocDataReceived)\n    {\n        return ATMO_GPS_NANO_Status_Fail;\n    }\n\n    memcpy(locData, &lastLocData, sizeof(lastLocData));\n\n    return ATMO_GPS_NANO_Status_Success;\n}\n"
	  }
	}
  },
  "md5": "4b28f53d5eeb543d5541a1cd8e50d95c"
}

Figure 3: Beginning of the nanoGPSClick EEL data in the .atmo file


3. Carve out the metadata.json file

The metadata.json file allows IoT Studio to understand the EEL’s functionality and properly display its abilities, triggers, properties, etc. As it turns out, the eelbuilder tool simply builds on this file by adding the driver files and ability code we provide to create the EEL. Therefore, we can simply remove these additions to get back to the original metadata.json file.

Start by saving the file we created in the previous step (Listing 1) as metadata.json. Go to the element abilities object and remove every key/value pair where the key is "code". Since this is the last key in each object, don't forget to delete the preceding commas (after "hidden": true) as well, as shown in Figure 4.


a) Before

b) After


Figure 4: Remove code from "abilities" objects

Delete the entire "files" object along with the "md5" key and value (Figure 5). Again, don't forget the preceding comma (after the square bracket this time).  


a) Before

b) After


Figure 5: Remove the "files" object and the "md5" checksum

4. Create directory structure for EEL source files

Using the EEL Builder tool, create the directory that will contain the EEL source files. By running the command node eelbulder.js --new --name="<eel-name>" --dir="<eel-directory>", a directory named "<eel-name>_EEL" will be created in the “eel-directory” directory. For example, I ran node eelbulder.js --new --name="nanoGPSClick" --dir=".", which created a directory named nanoGPSClick_EEL in the working directory. Figure 6 shows this as well as the contents of nanoGPSClick_EEL. The eelbuilder tool created a subdirectory structure for the driver files as well as a template metadata.json file. Replace this matadata.json file with the one we created in the previous step.

Figure 6: Run eelbuilder.js --new to create the EEL directory skeleton.

5. Create the abilities directory and files

Run the command node eelbulder.js --generate --dir="<eel-src-directory>" --dest="<eel-location>", where "eel-src-directory" is directory containing the EEL source code and "eel-location" is where the EEL file will be saved. Figure 7 shows the results of running the command node eelbulder.js --generate --dir="nanoGPSClick/" --dest=".". In the elements directory, it created a subdirectory called EmbeddedGpsClick, which came from the metadata.json file (.elements[0].type). In EmbeddedGpsClick it created the abilities directory and one .c file for each ability specified in metadata.json.

Figure 7: Run eelbuilder.js --generate to create and populate the abilities directory

Notice that I also got an error after running the command. Apparently, the element icon that was used for this sensor element (EmbeddedGpsClick.svg) is undocumented and therefore not recognized by the eelbuilder tool. Because I know the EmbeddedGpsClick.svg element is a valid option, I will use the --noicon option when I generate the EEL file in the final step. This will skip the icon validation operation.

6. Add code to ability files

The .c files in the abilities folder are used to populate the ability callback functions in the file app_src/atmosphere_callbacks.c (Listing 2) in the project source code. This is the file that is shown in IoT Studio IDE when the embedded code editor is used. Simply copy the code within each function that corresponds to an ability and paste it in its matching ability source file. Figure 8 shows the contents of these files after this is done.

Listing 2: atmosphere_callbacks.c from project source code downloaded in Step 1
#include "atmosphere_callbacks.h"

//HEADER START

//HEADER END

void ATMO_Setup() {

}


ATMO_Status_t GpsClick_trigger(ATMO_Value_t *in, ATMO_Value_t *out) {
	return ATMO_Status_Success;
}


ATMO_Status_t GpsClick_setup(ATMO_Value_t *in, ATMO_Value_t *out) {
ATMO_GPS_NANO_Config_t config;
config.reportFreq = ATMO_PROPERTY(GpsClick, reportFrequency);
config.powerPin = ATMO_PROPERTY(GpsClick, powerPin);
config.wupPin = ATMO_PROPERTY(GpsClick, wupPin);
config.gpioDriverInstance = ATMO_PROPERTY(GpsClick, gpioDriverInstance);
config.uartDriverInstance = ATMO_PROPERTY(GpsClick, uartDriverInstance);
config.rstPin = ATMO_PROPERTY(GpsClick, rstPin);

if(ATMO_GPS_NANO_Init(&config) != ATMO_GPS_NANO_Status_Success)
{
    return ATMO_Status_Fail;
}

ATMO_GPS_NANO_RegisterLocRxAbilityHandle(ATMO_ABILITY(GpsClick, locationReceived));
ATMO_GPS_NANO_RegisterLatRxAbilityHandle(ATMO_ABILITY(GpsClick, latitudeReceived));
ATMO_GPS_NANO_RegisterLongRxAbilityHandle(ATMO_ABILITY(GpsClick, longitudeReceived));

return ATMO_Status_Success;
}


ATMO_Status_t GpsClick_locationReceived(ATMO_Value_t *in, ATMO_Value_t *out) {
ATMO_CreateValueCopy(out, in);
return ATMO_Status_Success;
}


ATMO_Status_t GpsClick_latitudeReceived(ATMO_Value_t *in, ATMO_Value_t *out) {
ATMO_CreateValueCopy(out, in);
return ATMO_Status_Success;
}


ATMO_Status_t GpsClick_longitudeReceived(ATMO_Value_t *in, ATMO_Value_t *out) {
ATMO_CreateValueCopy(out, in);
return ATMO_Status_Success;
}

//FOOTER START

//FOOTER END

Figure 8: Contents of the ability source files

7. Copy the driver files

The driver code for the sensor element can be found in the project source code in the directory with the same name as the EEL. In my case, the folder nanoGPSClick contains two files: nanogps.c and nanogps.h. Some elements will contain more than two files. In any case, simply copy the .h files to the files/common/headers directory in the EEL source and the .c files to the files/common/objects directory. This completes the EEL source code and the final result is shown in Figure 9.

Figure 9: Final contents of the EEL source directory

8. Generate EEL file

Repeat the eelbuilder generate command you ran in Step 5 to update the EEL file with the source code. Figure 10 shows that by adding the --noicon option, I do not get the error shown in Figure 7 and the file nanoGPSClick.eel is created successfully.

Figure 10: Using the EEL source files to create the EEL file

Conclusion

To check your work, you should create another new project in IoT Studio and import the new EEL you just created. It should not look or behave any different than the original sensor element. After verifying its functionality, you can start tweaking the EEL source files to create an EEL for your unsupported sensor. Start by modifying the metadata.json file (change the library name, description, manufacturer, etc.). Add or remove abilities as you see fit, change the default values of properties, add your own driver files, and whatever else is needed to create a simple interface for the sensor.

Contact the Author

If you have any other questions/feedback, feel free to send an email to eewiki@digikey.com or visit Digi-Key's TechForum.



  • No labels