-
Notifications
You must be signed in to change notification settings - Fork 5
Home
Welcome to the gif_arduino wiki!
These pages describe the api structure.
- [Minimal] (Minimal API)
- [regular] (Regular API)
Some short descriptions on the main functionality of the libraries together with some sample code. Each topic also lists the API versions that are able to perform the described functionality.
No matter which version of the API you are using, setting up the connection with the IOT platform always has to be done. It is a little different for each API version, but there is always a common part that is valid for all.
#include <Ethernet.h> //for the pub/sub client
#include <PubSubClient.h> //sends data to the IOT platform
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient pubSub(mqttServer, 1883, callback, ethClient);
These statements should be put somewhere at the top of the sketch outside of any functions (they are includes & global decelerations).
The first 2 statements are includes for libraries that the script relies on. The Ethernet library provides basic tcp/ip communication. The PubSubClient library is used to provide 2 way pub-sub communication (mqtt). These includes need to be in your main script, otherwise the libraries are not correctly included in your sketch. Also, the pub-sub client has to be partly initiated in the main sketch (it can't be initiated from within the cloud library) - see further.
Next, the global ethernet object for the pub-sub client is declared. This is used by the mqtt library.
Finally, the mqtt callback is declared and the object is instantiated. The object uses the previously created ethernet object for it's communication. The 'mqttServer' parameter specifies the location of the server. this can be a dns name (char*) or an ip address (byte[]). After the 'mqttServer' parameter, the port is specified. Possible values are:
- 1883: the default port number for mqtt.
- 8883: the ssl enabled port number. ssl is not always supported on the arduino.
The callback is also used by the mqtt object. It's a function definition that has to be implemented further down in the code. This function is called every time that a value comes in from the cloud. See receiving data for more info.
ATTDevice Device("deviceId", "clientId");
void setup()
{
//set up your pins
Serial.begin(9600); // init serial link for debugging
Device.Subscribe(mac, pubSub); // setup receiving message from the iot platform (activate mqtt)
}
The first line creates the object that provides the connection to the cloud. It should also be placed somewhere at the top of your sketch, outside of any function (it's a global). The clientId & clientKey parameters (both are char*) can be found on the IOT website. They are assigned to you when you create your account.
[insert picture of website]
In the setup part of your sketch, after specifying the pinmode for your pins and optionally initializing the serial link, the connection with the IOT platform is prepared: the Ethernet library and the pub-sub client are started by calling 'Device.Subscribe'. The 'mac' parameter specifies the mac address of the arduino and the 'pubsub' parameter is the previously declared mqtt object. After calling this function, you are ready to send and receive values. You can't create any devices or assets with the minimal version of the library. These have to be created manually on the website.
ATTDevice Device("deviceId", "clientId", "clientKey");
void setup()
{
//set up your pins
Serial.begin(9600); // init serial link for debugging
if(Device.Connect(mac, httpServer)) // connect the device with the IOT platform.
{
//create assets here
Device.Subscribe(pubSub); // receive message from the iot platform (activate mqtt)
}
else
while(true);
}
As in the previous example, the first line creates the object that provides the connection to the cloud. It should also be placed somewhere at the top of your sketch, outside of any function (it's a global). The deviceId, clientId & clientKey parameters (all are char*) can be found on the IOT website. ClientId & clientKey are assigned to you when you create your account. The deviceId represents your arduino in the IOT platform and is created when you manually create the device in the IOT website.
[insert picture of device-create-website]
In the 'setup' part of your sketch, you initialize the connection with the IOT platform by calling 'Connect'. This expects the mac address of your ethernet card (byte[]) and the name of the HTTP server that it should use. If calling 'Connect' was successful, you can go ahead and create your assets dynamically. When this is done, the call to 'Device.Subscribe' will start the mqtt communication. It expects the pubsub object that you previously created.
Note that the setup will not succeed if the call to 'Device.Connect' failed. We don't want the script to do anything if we don't have a connection with the IOT platform. You could change this behavior if you like. Just make certain that you don't try to send any data to the IOT platform if there was no successful connection.
Available with the API versions:
- [regular] (Regular API)
With the regular version of the API, it is possible to dynamically create assets for your device. This procedure does use a fair amount of memory on the arduino, so it limits the number of assets that can be used on 1 device. If this is a problem, it's best to either use the minimal version of the library or switch to another arduino board that provides more resources like the arduino yun, mega or tre. Alternatively, you can also use an RPI and switch to the python library.
String sensorId = "1"; // uniquely identify this asset. Don't use spaces in the id.
String actuatorId = "2";
void setup()
{
if(Device.Connect(mac, httpServer))
{
Device.AddAsset(sensorId, F("sensorName"), F("your sensor description"), false, F("int"));
Device.AddAsset(actuatorId, F("actuatorName"), F("your actuator description"), true, F("bool"));
Device.Subscribe(pubSub);
}
else
while(true);
}
In this example, we first define 2 global strings that represent the id's of the sensors and actuators that we will use to send and receive values to and from the IOT platform. These are strings and not char* so that we can easily use the actuatorId's later on when we receive values from the IOT platform. See Receving data for more info. The sensorId's are also strings so that we only need 1 function for adding assets and sending values to the platform (saves a little memory).
Make certain that you select id's for your assets (sensors and actuators) that are unique within your script. Usually, you start at '1' and increment for each asset.
To make the assets available in the IOT platform, you call 'Device.AddAsset'. This will either create the asset or update it. So every time you call this function, any previous asset information in the IOT platform will be overwritten. Parameters are:
- the local id of the asset. This value should be unique within your script for each asset. The id of the device will be prepended to this value in order to make the id unique in the platform.
- the name of the asset. This is a char[].
- a description for the asset.
- True: the asset is an actuator (it can both send and receive values to/from the IOT platform). False: the asset is a sensor (it can only send values to the IOT platform).
- the type of the value that the asset produces/accepts. This can be: int, float, bool, string, DateTime (for allowed formats, see here), TimeSpan (for allowed formats, see here
Be careful: if you manually change the name, description or type information on the website, calling this function will overwrite these manual changes. So it's best to change this information from within your script if you decide to dynamically manage the assets.
Available with the API versions:
- [Minimal] (Minimal API)
- [regular] (Regular API)
unsigned long time; //only send every x amount of time.
void loop()
{
unsigned long curTime = millis();
if (curTime > (time + 5000)) // publish light reading every 5 seconds to sensor 1
{
unsigned int lightRead = analogRead(0); // read from light sensor (photocell)
Device.Send(String(lightRead), sensorId);
time = curTime;
}
}
This is a very basic code example for the main body of a sketch. It will periodically (every 5000 milliseconds) read an analog value from pin 0 and send it to the IOT platform. The main loop waits for 5 seconds before sending a new value so that we don't flood the system with too many values.
'Device.Send' is the function responsible for sending the value to the IOT platform. It takes 2 parameters: the value that needs to be sent, as a string and the Id of the asset for which we are sending a value. This can be a sensor or an actuator. Why send the value as a string? Well, there are 2 reasons, first off, the platform always expects a string, no matter what the actual data type is and this way, the library only needs 1 function for sending all possible value types without having to do any extra conversions internally, thus saving a little memory.
Available with the API versions:
- [Minimal] (Minimal API)
- [regular] (Regular API)
void loop()
{
//sensor processing
Device.Process(); //handle incoming data
}
// Callback function: handles messages that were sent from the iot platform to this device.
void callback(char* topic, byte* payload, unsigned int length)
{
String msgString;
{
//put this in a sub block, so any unused memory can be freed as soon as possible, required to save mem while sending data
char message_buff[length + 1]; //copy over the payload so that we can add a /0 terminator, this can then be wrapped inside a string for easy manipulation.
strncp(message_buff, (char*)payload, length); //copy over the data
message_buff[i] = '\0'; //make certain that it ends with a null
msgString = String(message_buff);
msgString.toLowerCase(); //make certain that our comparison later on works ok (it could be that a 'True' or 'False' was sent)
}
String* idOut = NULL;
{
//put this in a sub block, so any unused memory can be freed as soon as possible, required to save mem while sending data
String topicStr = topic; //we convert the topic to a string so we can easily work with it (use 'endsWith')
if (topicStr.endsWith(actuatorId)) //warning: the topic will always be lowercase. This allows us to work with multiple actuators: the name of the actuator to use is at the end of the topic.
{
if (msgString == "false") {
digitalWrite(ledPin, LOW); //change the led
idOut = &actuatorId;
}
else if (msgString == "true") {
digitalWrite(ledPin, HIGH);
idOut = &actuatorId;
}
}
}
if(idOut != NULL) //update the value on the IOT platform: provide feedback on the operation.
Device.Send(msgString, *idOut);
}
Ok, this is already a bit more interesting. A lot is happening here, not the least of which some optimisations to keep memory usage under control.
To start with, we go back to the 'loop' function. In order to receive values from the IOT platform to our device, we need to call 'Device.Process()'. This will check the mqtt connection, retrieve any values that are waiting and call the callback function that we specified at the beginning of our script while setting up the connection. So, if you don't want to receive any values from the IOT platform (not using any actuators), then you don't need to include this function.
Next is the actual callback function. This might look a bit daunting at first, but most of it is really boiler plate code that will never change. In order to explain things though, lets break it down into smaller parts:
String msgString;
{
//put this in a sub block, so any unused memory can be freed as soon as possible, required to save mem while sending data
char message_buff[length + 1]; //copy over the payload so that we can add a /0 terminator, this can then be wrapped inside a string for easy manipulation.
strncp(message_buff, (char*)payload, length); //copy over the data
message_buff[i] = '\0'; //make certain that it ends with a null
msgString = String(message_buff);
msgString.toLowerCase(); //make certain that our comparison later on works ok (it could be that a 'True' or 'False' was sent)
}
First the payload variable is converted into something that's easier to work with: a string. That's because the payload comes in as a raw byte array, but in reality, it's just a string value that has no terminating /0 character. So this is the first boilerplate code that will never change. As a small memory optimization, the whole section is put into its own code block so that the compiler can free the memory used for the temporary message_buff variable when it's no longer used.
As a final note about this code section: notice the call to 'toLowerCase' near the end. This is to make certain that any boolean values are always converted to lower case. In some situations a boolean value could arrive as 'True' or 'False', but not always. In order to make the comparison for the value easier later on, we convert it to a lower case value. You can remove this line if this is giving you problems for receiving string values where you want to preserve the case formatting.
String* idOut = NULL;
{
//put this in a sub block, so any unused memory can be freed as soon as possible, required to save mem while sending data
String topicStr = topic; //we convert the topic to a string so we can easily work with it (use 'endsWith')
if (topicStr.endsWith(actuatorId)) //warning: the topic will always be lowercase. This allows us to work with multiple actuators: the name of the actuator to use is at the end of the topic.
{
if (msgString == "false") {
digitalWrite(ledPin, LOW); //change the led
idOut = &actuatorId;
}
else if (msgString == "true") {
digitalWrite(ledPin, HIGH);
idOut = &actuatorId;
}
}
}
if(idOut != NULL)
Device.Send(msgString, *idOut);
The next code block will do the actual processing of the value and send it to the actuator. This is the part of the code that you will most likely have to change the most.
So what's happening here? Well the idea is simple: you need to check the topic to find out which actuator is receiving a value. The topic string will always end with the id of the actuator, so we use 'endsWith()' to check the actuator that is being controlled. Once we have found that, we need to send the actual value. Because the incoming value is a string, we need to convert it to the data type that the actuator expects. In the example above, we are working with a boolean, so there's an extra if statement to see if the incoming value is 'true' or false and call 'digitalWrite' with the appropriate value.
There is 1 more thing going on, that's again a small memory optimization for the final step: we want to let the IOT platform know that the value was correctly processed by calling 'Device.Send' again like in the next bit of code. Instead