The Parse platform provides a complete backend solution for your hardware device. Our goal is to totally eliminate the need for writing server code or maintaining servers. Using our C SDKs, all it takes is a few lines of code to save and retrieve data from the Parse Server.
We provide two open source reference implementations for embedded environments:
Both these SDKs provide a consistent interface to interact with the Parse REST API, but with different underlying implementations.
On Parse, you create an App for each of your mobile and embedded applications. Each App has its own application ID and client key that you apply to your SDK install. Your account on Parse can accommodate multiple Apps. This is useful even if you have one application, since you can deploy different versions for test and production.
Run the following lines in your Linux terminal:
sudo apt-get update
sudo apt-get install autoconf automake libtool
sudo apt-get install libcurl4-openssl-dev uuid-dev
git clone https://github.com/parse-community/parse-embedded-sdks.git
Next, navigate to the newly downloaded directory and run a few commands to build the SDK. After this, the C library will be available globally on device.
cd parse-embedded-sdks
touch README
autoreconf -fi
./configure --prefix=/usr
make
sudo make install
In order for Parse to know which app is associated with the connected device, simply specify the application ID and client key in the device code:
ParseClient client = parseInitialize("${APPLICATION_ID}", "${CLIENT_KEY}");
After this, all calls to the Parse Server will use the specified app.
Storing data through the Parse REST API is built around a JSON encoding of the object’s data. This data is schemaless, which means that you don’t need to specify ahead of time what keys exist on each object. You simply set whatever key-value pairs you want, and the backend will store it.
For example, let’s say you’re tracking data for your smart toaster. A single Parse Object could contain:
temperature: 175.0, leverDown: true
Keys must be alphanumeric strings. Values can be strings, numbers, booleans, or even arrays and dictionaries - anything that can be JSON-encoded.
Each object has a class name that you can use to distinguish different sorts of data. For example, we store the temperature data in a class called Temperature
. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.
So far we’ve used values with type double
and bool
. The Parse REST API also supports strings, arrays, dates, and more. Read more about representing these and other types as JSON in the REST API Guide.
The main way you’ll be interacting with Parse is through the parseSendRequest
function, which sends a request to the REST API. For example, here’s how to save an object with some data:
char data[] = "{ \"temperature\": 165 }"; parseSendRequest(client, "POST", "/parse/classes/Temperature", data, NULL);
For some requests you will be interested in data returned for the request. In such a case you need to setup a callback and pass it to parseSendRequest
.
void mySaveCallback(ParseClient client, int error, int httpStatus, const char* httpResponseBody) {
if (error == 0 && httpResponseBody != NULL) {
// httpResponseBody holds the response to the request
}
}
parseSendRequest(client, "GET", "/parse/classes/TestObject/gsMHOY3MAx", NULL, myCallback);
Using this function, you have full access to the REST API to create objects, delete objects, send analytics events, and more. Take a look at the REST API Guide to find out all the details.
At the core of many apps, there is a notion of user accounts that lets users access their information in a secure manner. In our other SDKs, we provide a specialized user class that automatically handles much of the functionality required for user account management. Users are a special class of Parse Objects and has all the same features, such as flexible schema, automatic persistence, and a key value interface.
You can sign in users via the REST API, but we do not recommend doing so unless your hardware device actually provides user keyboard input. In some cases, you may want to have a companion mobile or desktop app that lets the user sign up, and, that app creates a restricted session for the device running the embedded SDK. During your hardware device provisioning process, the phone can send this restricted session’s token to the device. You can read more about users in our REST API or one of our other SDK guides.
Once you have a session token, you can use it to act on the behalf of a particular user. The session token used by the SDK can be set withparseSetSessionToken
:
char token[] = "r:olqZkbv8fefVFNjWegyIXIggd";
parseSetSessionToken(client, token);
Once the session token is set, it will be associated with the current installation if it is not already associated with an installation. This association can be done only once and is automatically done by the SDK.
A session token is tied to a specific installation (specified by the installationId field on the Session object). Attempts to use a session token for another installation will result in errors.
If a session token is set before an installation is set, the SDK will create an installation for you. Thus, if you have a specific pair of installation ID and session token you need to use, you should set the installation ID first.
The session token can be cleared withparseClearSessionToken
function. Once the token is cleared, the device will not be authenticated as the user anymore:
parseClearSessionToken(client);
One can get the current session token by doing:
char* session_token = parseGetSessionToken(client);
The session token will be persistent across reboots. Note that we highly recommend using Restricted Sessions on hardware devices, especially ones that do not provide a high level of client security. For more details, check out the guide on Sessions.
Using Push Notifications, you’ll be able to send realtime notifications to your device. This can be triggered by changes in your data, custom code in Cloud Code, a companion mobile app, and much more.
Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, you could send a connected thermostat a push to change the desired temperature.
There are two ways to create an installation in conjunction with your hardware device. You can generate an installation ID (random lowercase UUID) elsewhere (e.g. phone), send that to your hardware device during initial provisioning, then set the installation ID on the hardward device:
char data[] = "ab946c14-757a-4448-8b77-69704b01bb7b";
parseSetInstallationId(client, data);
The installation ID is a unique identifier for the device, so you should make sure to assign different installation IDs to different devices (i.e. your UUID generator has enough randomness). After you do the above, the device will automatically create an Installation object with this installation ID.
If you do not pass in an installation ID, the SDK will automatically generate an installation ID for you, and create an Installation object with it upon the first request sent to Parse. There are several events that will trigger this:parseGetInstallationId
,parseSetSessionToken
,parseSendRequest
, andparseStartPushService
.
You can retrieve your installation ID with theparseGetInstallationId
function:
char* installation_id = parseGetInstallationId(client);
The installation ID is persisted across reboots.
The Installation class has several special fields that help you manage and target devices. The relevant ones for embedded devices are:
channels
: An array of the channels to which a device is currently subscribed.deviceType
: The type of device, “ios”, “android”, “winrt”, “winphone”, “dotnet”, or “embedded” (readonly).installationId
: Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app’s installations.(readonly).appName
: The display name of the client application to which this installation belongs.appVersion
: The version string of the client application to which this installation belongs.parseVersion
: The version of the Parse SDK which this installation uses.In order to subscribe to push notifications, you’ll need to start the push service and define how your device will handle a push when it is received.
First, we define the push callback function:
void myPushCallback(ParseClient client, int error, const char *buffer) {
if (error == 0 && buffer != NULL) {
printf("push: '%s'\n", buffer);
}
}
Then, we set the callback and start the push service loop:
parseSetPushCallback(client, myPushCallback);
parseStartPushService(client);
parseRunPushLoop(client);
In cases where there is already an explicit application loop, we can integrate push into it using a file handle obtained with theparseGetPushSelectHandle
function. Then we call theparseProcessNextPushEvent
function on each iteration of the loop (not only when there is data on the file descriptor). TheparseProcessNextPushEvent
function will call the push callback if there is a new push message.
To do this we must first include the respective libraries:
#include <sys/time.h>
Then add the following logic to your method:
// ...
parseSetPushCallback(client, myPushCallback);
parseStartPushService(client);
int socket = parseGetPushSocket(client);
while(1) {
struct timeval tv;
fd_set receive, send, error;
// tv_sec defines the interval at which the method is executed.
// The lower the value the more responsive it will be to notifications.
tv.tv_sec = 10;
tv.tv_usec= 0;
FD_ZERO(&receive);
FD_ZERO(&send);
FD_ZERO(&error);
FD_SET(socket, &error);
FD_SET(socket, &receive);
select(socket + 1, &receive, &send, &error, &tv);
// ...
parseProcessNextPushNotification(client);
}
Cloud Functions allow you to run custom app logic on your Parse Server. This is especially useful for running complex app logic in the cloud so that you can reduce the memory footprint of your code on the IoT device. In a Cloud Function, you can query/save Parse data, send push notifications, and log analytics events.
You write your Cloud Code in JavaScript using the Parse JavaScript SDK. See our Cloud Code guide for details.
For example, you define a Cloud Function as below.
Parse.Cloud.define("hello", request => {
return request.body;
});
Then you can invoke this Cloud Function from your device:
void myCloudFunctionCallback(ParseClient client, int error, int httpStatus, const char* httpResponseBody) {
if (error == 0 && httpResponseBody != NULL) {
// httpResponseBody holds the Cloud Function response
}
}
parseSendRequest(client, "POST", "/parse/functions/hello", "{\"value\":\"echo\"}", myCloudFunctionCallback);
We prepared a sample app that demonstrates how to provision connected devices using a companion phone app such that connected devices can securely access user-specific data on Parse Server. This sample app also demonstrates how to send push notifications between the phone app and connected devices.
There are two main ways an error from the SDK can be propagated to the caller - as a return result of the method call, or through the request and push callbacks.
All functions that return a result will return 0 on success and a non-zero value on error. The error value will be an OS-specific error (for example, socket errors).
The errors passed through callbacks are again OS-specific errors. The one exception is if there is an HTTP error status (4xx or 5xx) for a request, in which case, the HTTP status will be passed as separate parameters to the request callback.
In the case of an HTTP error status, you should also check the request body. If the request body contains a valid JSON document, the document will contain a Parse error code, as defined by the REST API documentation.
For a list of all possible error codes, scroll down to Error Codes.
The following is a list of all the error codes that can be returned by the Parse API. You may also refer to RFC2616 for a list of http error codes. Make sure to check the error message for more details.
Name | Code | Description |
---|---|---|
UserInvalidLoginParams |
101 | Invalid login parameters. Check error message for more details. |
ObjectNotFound |
101 | The specified object or session doesn’t exist or could not be found. Can also indicate that you do not have the necessary permissions to read or write this object. Check error message for more details. |
InvalidQuery |
102 | There is a problem with the parameters used to construct this query. This could be an invalid field name or an invalid field type for a specific constraint. Check error message for more details. |
InvalidClassName |
103 | Missing or invalid classname. Classnames are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. |
MissingObjectId |
104 | An unspecified object id. |
InvalidFieldName |
105 | An invalid field name. Keys are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. Some field names may be reserved. Check error message for more details. |
InvalidPointer |
106 | A malformed pointer was used. You would typically only see this if you have modified a client SDK. |
InvalidJSON |
107 | Badly formed JSON was received upstream. This either indicates you have done something unusual with modifying how things encode to JSON, or the network is failing badly. Can also indicate an invalid utf-8 string or use of multiple form encoded values. Check error message for more details. |
CommandUnavailable |
108 | The feature you tried to access is only available internally for testing purposes. |
NotInitialized |
109 | You must call Parse.initialize before using the Parse library. Check the Quick Start guide for your platform. |
ObjectTooLarge |
116 | The object is too large. |
ExceededConfigParamsError |
116 | You have reached the limit of 100 config parameters. |
InvalidLimitError |
117 | An invalid value was set for the limit. Check error message for more details. |
InvalidSkipError |
118 | An invalid value was set for skip. Check error message for more details. |
OperationForbidden |
119 | The operation isn’t allowed for clients due to class-level permissions. Check error message for more details. |
CacheMiss |
120 | The result was not found in the cache. |
InvalidNestedKey |
121 | An invalid key was used in a nested JSONObject. Check error message for more details. |
InvalidACL |
123 | An invalid ACL was provided. |
InvalidEmailAddress |
125 | The email address was invalid. |
DuplicateValue |
137 | Unique field was given a value that is already taken. |
InvalidRoleName |
139 | Role’s name is invalid. |
ReservedValue |
139 | Field value is reserved. |
ExceededCollectionQuota |
140 | You have reached the quota on the number of classes in your app. Please delete some classes if you need to add a new class. |
ScriptFailed |
141 | Cloud Code script failed. Usually points to a JavaScript error. Check error message for more details. |
FunctionNotFound |
141 | Cloud function not found. Check that the specified Cloud function is present in your Cloud Code script and has been deployed. |
JobNotFound |
141 | Background job not found. Check that the specified job is present in your Cloud Code script and has been deployed. |
ValidationFailed |
142 | Cloud Code validation failed. |
WebhookError |
143 | Webhook error. |
InvalidImageData |
150 | Invalid image data. |
UnsavedFileError |
151 | An unsaved file. |
InvalidPushTimeError |
152 | An invalid push time was specified. |
HostingError |
158 | Hosting error. |
InvalidEventName |
160 | The provided analytics event name is invalid. |
ClassNotEmpty |
255 | Class is not empty and cannot be dropped. |
AppNameInvalid |
256 | App name is invalid. |
MissingAPIKeyError |
902 | The request is missing an API key. |
InvalidAPIKeyError |
903 | The request is using an invalid API key. |
Name | Code | Description |
---|---|---|
IncorrectType |
111 | A field was set to an inconsistent type. Check error message for more details. |
InvalidChannelName |
112 | Invalid channel name. A channel name is either an empty string (the broadcast channel) or contains only a-zA-Z0-9_ characters and starts with a letter. |
InvalidSubscriptionType |
113 | Bad subscription type. Check error message for more details. |
InvalidDeviceToken |
114 | The provided device token is invalid. |
PushMisconfigured |
115 | Push is misconfigured in your app. Check error message for more details. |
PushWhereAndChannels |
115 | Can’t set channels for a query-targeted push. You can fix this by moving the channels into your push query constraints. |
PushWhereAndType |
115 | Can’t set device type for a query-targeted push. You can fix this by incorporating the device type constraints into your push query. |
PushMissingData |
115 | Push is missing a ‘data’ field. |
PushMissingChannels |
115 | Non-query push is missing a ‘channels’ field. Fix by passing a ‘channels’ or ‘query’ field. |
ClientPushDisabled |
115 | Client-initiated push is not enabled. Check your Parse app’s push notification settings. |
RestPushDisabled |
115 | REST-initiated push is not enabled. Check your Parse app’s push notification settings. |
ClientPushWithURI |
115 | Client-initiated push cannot use the “uri” option. |
PushQueryOrPayloadTooLarge |
115 | Your push query or data payload is too large. Check error message for more details. |
InvalidExpirationError |
138 | Invalid expiration value. |
MissingPushIdError |
156 | A push id is missing. Deprecated. |
MissingDeviceTypeError |
157 | The device type field is missing. Deprecated. |
Name | Code | Description |
---|---|---|
InvalidFileName |
122 | An invalid filename was used for Parse File. A valid file name contains only a-zA-Z0-9_. characters and is between 1 and 128 characters. |
MissingContentType |
126 | Missing content type. |
MissingContentLength |
127 | Missing content length. |
InvalidContentLength |
128 | Invalid content length. |
FileTooLarge |
129 | File size exceeds maximum allowed. |
FileSaveError |
130 | Error saving a file. |
FileDeleteError |
153 | File could not be deleted. |
FileDeleteUnnamedError |
161 | Unnamed file could not be deleted. |
Name | Code | Description |
---|---|---|
InvalidInstallationIdError |
132 | Invalid installation id. |
InvalidDeviceTypeError |
133 | Invalid device type. |
InvalidChannelsArrayError |
134 | Invalid channels array value. |
MissingRequiredFieldError |
135 | Required field is missing. |
ChangedImmutableFieldError |
136 | An immutable field was changed. |
Name | Code | Description |
---|---|---|
ReceiptMissing |
143 | Product purchase receipt is missing. |
InvalidPurchaseReceipt |
144 | Product purchase receipt is invalid. |
PaymentDisabled |
145 | Payment is disabled on this device. |
InvalidProductIdentifier |
146 | The product identifier is invalid. |
ProductNotFoundInAppStore |
147 | The product is not found in the App Store. |
InvalidServerResponse |
148 | The Apple server response is not valid. |
ProductDownloadFilesystemError |
149 | The product fails to download due to file system error. |
Name | Code | Description |
---|---|---|
UsernameMissing |
200 | The username is missing or empty. |
PasswordMissing |
201 | The password is missing or empty. |
UsernameTaken |
202 | The username has already been taken. |
UserEmailTaken |
203 | Email has already been used. |
UserEmailMissing |
204 | The email is missing, and must be specified. |
UserWithEmailNotFound |
205 | A user with the specified email was not found. |
SessionMissing |
206 | A user object without a valid session could not be altered. |
MustCreateUserThroughSignup |
207 | A user can only be created through signup. |
AccountAlreadyLinked |
208 | An account being linked is already linked to another user. |
InvalidSessionToken |
209 | The device’s session token is no longer valid. The application should ask the user to log in again. |
Name | Code | Description |
---|---|---|
LinkedIdMissing |
250 | A user cannot be linked to an account because that account’s id could not be found. |
InvalidLinkedSession |
251 | A user with a linked (e.g. Facebook or Twitter) account has an invalid session. Check error message for more details. |
InvalidGeneralAuthData |
251 | Invalid auth data value used. |
BadAnonymousID |
251 | Anonymous id is not a valid lowercase UUID. |
FacebookBadToken |
251 | The supplied Facebook session token is expired or invalid. |
FacebookBadID |
251 | A user with a linked Facebook account has an invalid session. |
FacebookWrongAppID |
251 | Unacceptable Facebook application id. |
TwitterVerificationFailed |
251 | Twitter credential verification failed. |
TwitterWrongID |
251 | Submitted Twitter id does not match the id associated with the submitted access token. |
TwitterWrongScreenName |
251 | Submitted Twitter handle does not match the handle associated with the submitted access token. |
TwitterConnectFailure |
251 | Twitter credentials could not be verified due to problems accessing the Twitter API. |
UnsupportedService |
252 | A service being linked (e.g. Facebook or Twitter) is unsupported. Check error message for more details. |
UsernameSigninDisabled |
252 | Authentication by username and password is not supported for this application. Check your Parse app’s authentication settings. |
AnonymousSigninDisabled |
252 | Anonymous users are not supported for this application. Check your Parse app’s authentication settings. |
FacebookSigninDisabled |
252 | Authentication by Facebook is not supported for this application. Check your Parse app’s authentication settings. |
TwitterSigninDisabled |
252 | Authentication by Twitter is not supported for this application. Check your Parse app’s authentication settings. |
InvalidAuthDataError |
253 | An invalid authData value was passed. Check error message for more details. |
LinkingNotSupportedError |
999 | Linking to an external account not supported yet with signup_or_login. Use update instead. |
Name | Code | Description |
---|---|---|
ConnectionFailed |
100 | The connection to the Parse servers failed. |
AggregateError |
600 | There were multiple errors. Aggregate errors have an “errors” property, which is an array of error objects with more detail about each error that occurred. |
FileReadError |
601 | Unable to read input for a Parse File on the client. |
XDomainRequest |
602 | A real error code is unavailable because we had to use an XDomainRequest object to allow CORS requests in Internet Explorer, which strips the body from HTTP responses that have a non-2XX status code. |
Name | Code | Description |
---|---|---|
RequestTimeout |
124 | The request was slow and timed out. Typically this indicates that the request is too expensive to run. You may see this when a Cloud function did not finish before timing out, or when a Parse.Cloud.httpRequest connection times out. |
InefficientQueryError |
154 | An inefficient query was rejected by the server. Refer to the Performance Guide and slow query log. |
Name | Code | Description |
---|---|---|
OtherCause |
-1 | An unknown error or an error unrelated to Parse occurred. |
InternalServerError |
1 | Internal server error. No information available. |
ServiceUnavailable |
2 | The service is currently unavailable. |
ClientDisconnected |
4 | Connection failure. |
In order to provide better organization and avoid conflicts with Parse Platform’s built-in Parse.Error
codes, the following ranges are defined:
<= 4999
(including negative numbers)>= 5000 and <= 8999
>= 9000 and <= 9999
1
and add any specific information in the error message, or use another pre-defined error code of Parse Platform.