Step 6: Data Shapes Data Shapes are an important part of creating/firing Events and also invoking Services Define With Macros In order to define a Data Shape using a macro, use TW_MAKE_DATASHAPE. NOTE: The macros are all defined in the twMacros.h header file. TW_MAKE_DATASHAPE("SteamSensorReadingShape",
TW_DS_ENTRY("ActivationTime", TW_NO_DESCRIPTION ,TW_DATETIME),
TW_DS_ENTRY("SensorName", TW_NO_DESCRIPTION ,TW_NUMBER),
TW_DS_ENTRY("Temperature", TW_NO_DESCRIPTION ,TW_NUMBER),
TW_DS_ENTRY("Pressure", TW_NO_DESCRIPTION ,TW_NUMBER),
TW_DS_ENTRY("FaultStatus", TW_NO_DESCRIPTION ,TW_BOOLEAN),
TW_DS_ENTRY("InletValve", TW_NO_DESCRIPTION ,TW_BOOLEAN),
TW_DS_ENTRY("TemperatureLimit", TW_NO_DESCRIPTION ,TW_NUMBER),
TW_DS_ENTRY("TotalFlow", TW_NO_DESCRIPTION ,TW_INTEGER)
); Define Without Macros In order to define a Data Shape without using a macro, use the twDataShape_CreateFromEntries function. In the example below, we are creating a Data Shape called SteamSensorReadings that has two numbers as Field Definitions. twDataShape * ds = twDataShape_Create(twDataShapeEntry_Create("a",NULL,TW_NUMBER));
twDataShape_AddEntry(ds, twDataShapeEntry_Create("b",NULL,TW_NUMBER));
/* Name the DataShape for the SteamSensorReadings service output */
twDataShape_SetName(ds, "SteamSensorReadings"); Step 7: Events and Services Events and Services provide useful functionality. Events are a good way to make a Service be asynchronous. You can call a Service, let it return, then your Entity can subscribe to your Event and not keep the original Service function waiting. Events are also a good way to allow the platform to respond to data when it arrives on the edge device without it having to poll the edge device for updates. Fire Events To fire an Event you first need to register the Event and load it with the necessary fields for the Data Shape of that Event using the twApi_RegisterEvent function. Afterwards, you would send a request to the ThingWorx server with the collected values using the twApi_FireEvent function. An example is as follows: twDataShape * ds = twDataShape_Create(twDataShapeEntry_Create("message", NULL,TW_STRING));
/* Event datashapes require a name */
twDataShape_SetName(ds, "SteamSensorFault");
/* Register the service */
twApi_RegisterEvent(TW_THING, thingName, "SteamSensorFault", "Steam sensor event", ds);
….
struct {
char FaultStatus;
double Temperature;
double TemperatureLimit;
} properties;
….
properties. TemperatureLimit = rand() + RAND_MAX/5.0;
properties.Temperature = rand() + RAND_MAX/5.0;
properties.FaultStatus = FALSE;
if (properties.Temperature > properties.TemperatureLimit && properties.FaultStatus == FALSE) {
twInfoTable * faultData = 0;
char msg[140];
properties.FaultStatus = TRUE;
sprintf(msg,"%s Temperature %2f exceeds threshold of %2f",
thingName, properties.Temperature, properties.TemperatureLimit);
faultData = twInfoTable_CreateFromString("message", msg, TRUE);
twApi_FireEvent(TW_THING, thingName, "SteamSensorFault", faultData, -1, TRUE);
twInfoTable_Delete(faultData);
} Invoke Services In order to invoke a Service, you will use the twApi_InvokeService function. The full documentation for this function can be found in [C SDK HOME DIR]/src/api/twApi.h. Refer to the table below for additional information. Parameter Type Description entityType Input The type of Entity that the service belongs to. Enumeration values can be found in twDefinitions.h. entityName Input The name of the Entity that the service belongs to. serviceName Input The name of the Service to execute. params Input A pointer to an Info Table containing the parameters to be passed into the Service. The calling function will retain ownership of this pointer and is responsible for cleaning up the memory after the call is complete. result Input/Output A pointer to a twInfoTable pointer. In a successful request, this parameter will end up with a valid pointer to a twInfoTable that is the result of the Service invocation. The caller is responsible for deleting the returned primitive using twInfoTable_Delete. It is possible for the returned pointer to be NULL if an error occurred or no data is returned. timeout Input The time (in milliseconds) to wait for a response from the server. A value of -1 uses the DEFAULT_MESSAGE_TIMEOUT as defined in twDefaultSettings.h. forceConnect Input A Boolean value. If TRUE and the API is in the disconnected state of the duty cycle, the API will force a reconnect to send the request. See below for an example in which the Copy service from the FileTransferSubsystem is called: twDataShape * ds = NULL;
twInfoTable * it = NULL;
twInfoTableRow * row = NULL;
twInfoTable * transferInfo = NULL;
int res = 0;
const char * sourceRepo = "SimpleThing_1";
const char * sourcePath = "tw/hotfolder/";
const char * sourceFile = "source.txt";
const char * targetRepo = "SystemRepository";
const char * targetPath = "/";
const char * targetFile = "source.txt";
uint32_t timeout = 60;
char asynch = TRUE;
char * tid = 0;
/* Create an infotable out of the parameters */
ds = twDataShape_Create(twDataShapeEntry_Create("sourceRepo", NULL, TW_STRING));
res = twDataShape_AddEntry(ds, twDataShapeEntry_Create("sourcePath", NULL, TW_STRING));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("sourceFile", NULL, TW_STRING));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("targetRepo", NULL, TW_STRING));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("targetPath", NULL, TW_STRING));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("targetFile", NULL, TW_STRING));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("async", NULL, TW_BOOLEAN));
res |= twDataShape_AddEntry(ds, twDataShapeEntry_Create("timeout", NULL, TW_INTEGER));
it = twInfoTable_Create(ds);
row = twInfoTableRow_Create(twPrimitive_CreateFromString(sourceRepo, TRUE));
res = twInfoTableRow_AddEntry(row, twPrimitive_CreateFromString(sourcePath, TRUE));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromString(sourceFile, TRUE));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromString(targetRepo, TRUE));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromString(targetPath, TRUE));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromString(targetFile, TRUE));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromBoolean(asynch));
res |= twInfoTableRow_AddEntry(row, twPrimitive_CreateFromInteger(timeout));
twInfoTable_AddRow(it,row);
/* Make the service call */
res = twApi_InvokeService(TW_SUBSYSTEM, "FileTransferSubsystem", "Copy", it, &transferInfo, timeout ? (timeout * 2): -1, FALSE);
twInfoTable_Delete(it);
/* Grab the tid */
res = twInfoTable_GetString(transferInfo,"transferId",0, &tid); Bind Event Handling You may want to track exactly when your edge Entities are successfully bound to or unbound from the server. The reason for this is that only bound items should be interacting with the ThingWorx Platform and the ThingWorx Platform will never send any requests targeted at an Entity that is not bound. A simple example that only logs the bound Thing can be seen below. After creating this function, it will need to be registered using the twApi_RegisterBindEventCallback function before the connection is made. void BindEventHandler(char * entityName, char isBound, void * userdata) {
if (isBound) TW_LOG(TW_FORCE,"BindEventHandler: Entity %s was Bound", entityName);
else TW_LOG(TW_FORCE,"BindEventHandler: Entity %s was Unbound", entityName);
}
….
twApi_RegisterBindEventCallback(thingName, BindEventHandler, NULL); OnAuthenticated Event Handling You may also want to know exactly when your Edge device has successfully authenticated and made a connection to the ThingWorx platform. Like the bind Event handling, this function will need to be made and registered. To register this handler, use the twApi_RegisterOnAuthenticated Callback function before the connection is made. This handler form can also be used to do a delay bind for all Things. void AuthEventHandler(char * credType, char * credValue, void * userdata) {
if (!credType || !credValue) return;
TW_LOG(TW_FORCE,"AuthEventHandler: Authenticated using %s = %s. Userdata = 0x%x", credType, credValue, userdata);
/* Could do a delayed bind here */
/* twApi_BindThing(thingName); */
}
…
twApi_RegisterOnAuthenticatedCallback(AuthEventHandler, NULL); Step 8: Tasks If you are using the built-in Tasker to drive data collection or other types of repetitive or periodic activities, create a function for the task. Task functions are registered with the Tasker and then called at the rate specified after they are registered. The Tasker is a very simple, cooperative multitasker, so these functions should not take long to return and most certainly must not go into an infinite loop. The signature for a task function is found in [C SDK HOME DIR]/src/utils/twTasker.h. The function is passed a DATETIME value with the current time and a void pointer that is passed into the Tasker when the task is registered. After creating this function, it will need to be registered using the twApi_CreateTask function after the connection is created. Below shows an example of creating this function, registering this function, and how this function can be used. #define DATA_COLLECTION_RATE_MSEC 2000
void dataCollectionTask(DATETIME now, void * params) {
/* TW_LOG(TW_TRACE,"dataCollectionTask: Executing"); */
properties.TotalFlow = rand()/(RAND_MAX/10.0);
properties.Pressure = 18 + rand()/(RAND_MAX/5.0);
properties.Location.latitude = properties.Location.latitude + ((double)(rand() - RAND_MAX))/RAND_MAX/5;
properties.Location.longitude = properties.Location.longitude + ((double)(rand() - RAND_MAX))/RAND_MAX/5;
properties.Temperature = 400 + rand()/(RAND_MAX/40);
/* Check for a fault. Only do something if we haven't already */
if (properties.Temperature > properties.TemperatureLimit && properties.FaultStatus == FALSE) {
twInfoTable * faultData = 0;
char msg[140];
properties.FaultStatus = TRUE;
properties.InletValve = TRUE;
sprintf(msg,"%s Temperature %2f exceeds threshold of %2f",
thingName, properties.Temperature, properties.TemperatureLimit);
faultData = twInfoTable_CreateFromString("message", msg, TRUE);
twApi_FireEvent(TW_THING, thingName, "SteamSensorFault", faultData, -1, TRUE);
twInfoTable_Delete(faultData);
}
/* Update the properties on the server */
sendPropertyUpdate();
}
…
twApi_CreateTask(DATA_COLLECTION_RATE_MSEC, dataCollectionTask);
…
while(1) {
char in = 0;
#ifndef ENABLE_TASKER
DATETIME now = twGetSystemTime(TRUE);
twApi_TaskerFunction(now, NULL);
twMessageHandler_msgHandlerTask(now, NULL);
if (twTimeGreaterThan(now, nextDataCollectionTime)) {
dataCollectionTask(now, NULL);
nextDataCollectionTime = twAddMilliseconds(now, DATA_COLLECTION_RATE_MSEC);
}
#else
in = getch();
if (in == 'q') break;
else printf("\n");
#endif
twSleepMsec(5);
} Click here to view Part 4 of this guide
View full tip