⚠️ The Arduino SDK has been retired due to a lack of contribution and use. If you intent to continue using this SDK you can still fork the repo. If you are interested in maintaining it please make yourself known and we would be happy to unarchive it.
If you haven’t installed the SDK yet, please head over to the Arduino SDK repository to install the SDK in the Arduino IDE. Note that this SDK requires the Arduino Yún and the Arduino IDE v1.6.0+.
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.
Note that the Arduino SDK only contains a subset of Parse features found in mobile and desktop. This allows the SDK to be smaller in size and more performant, making it suitable for constrained embedded environments. The Arduino SDK is fully open source, and anyone can contribute to make it better, or make their own changes if necessary. Check out the GitHub repository for more information.
In order for Parse to know which app is associated with the Arduino device, simply specify the application ID and client key in your setup
function:
void setup() {
Parse.begin("${APPLICATION_ID}", "${CLIENT_KEY}");
// ...
}
After this, all calls to the Parse Server will use the specified app.
In order for the Arduino device to know where the Parse server is located, specify the URL in your setup
function:
void setup() {
Parse.begin("${APPLICATION_ID}", "${CLIENT_KEY}");
Parse.setServerURL("parseapi.back4app.com");
// ...
}
When the Arduino attempts to connect to the Parse server, it will need to know whether or not to connect using SSL. It is recommended to use SSL whenever possible.
Option 1: SSL
SSL requires that the Arduino know the server fingerprint. The server fingerprint can be found using the Chrome browser. Browse the secure server URL, press ctrl+shift+i
, select security
tab, click view certificate
, and get the thumbprint from the details
tab. Specify this as well in your setup
function:
void setup() {
Parse.begin("${APPLICATION_ID}", "${CLIENT_KEY}");
Parse.setServerURL("parseapi.back4app.com");
Parse.setHostFingerprint("${HOST_THUMBPRINT}");
// ...
}
Option 2: Insecure
The Arduino can connect to Parse over an insecure connection if desired, but must be specified in your setup function:
void setup() {
Parse.begin("${APPLICATION_ID}", "${CLIENT_KEY}");
Parse.setServerURL("parseapi.back4app.com");
Parse.setClientInsecure();
// ...
}
Parse is now initialized and ready to make calls to the Parse server.
Storing data on Parse is built around the Parse Object. Each Parse Object contains key-value pairs of JSON-compatible 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 our 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.
Let’s say you want to save the Temperature
described above to your Parse Server. You would do the following:
ParseObjectCreate create;
create.setClassName("Temperature");
create.add("temperature", 175.0);
create.add("leverDown", true);
ParseResponse response = create.send();
if (!response.getErrorCode()) {
// The object has been saved
} else {
// There was a problem, check response.
getErrorCode();
}
response.close(); // Free the resource
After this code runs, you will probably be wondering if anything really happened. To make sure the data was saved, you can look at the Data Browser in your app on Parse. You should see something like this:
{
"objectId": "xWMyZ4YEGZ",
"score": 1337,
"playerName": "Sean Plott",
"cheatMode": false,
"createdAt":"2022-01-01T12:23:45.678Z",
"updatedAt":"2022-01-01T12:23:45.678Z"
}
There are two things to note here. You didn’t have to configure or set up a new Class called Temperature
before running this code. Your Parse app lazily creates this Class for you when it first encounters it.
There are also a few fields you don’t need to specify that are provided as a convenience. objectId
is a unique identifier for each saved object. createdAt
andupdatedAt
represent the time that each object was created and last modified in your Parse Server. Each of these fields is filled in by Parse, so they don’t exist on a Parse Object until a save operation has completed.
Saving data to the cloud is fun, but it’s even more fun to get that data out again. If the object has been uploaded to the server, you can use the objectId
to retrieve it using a query:
ParseObjectGet get;
get.setClassName("Temperature");
get.setObjectId("xWMyZ4YEGZ");
ParseResponse response = get.send();
double temp = response.getDouble("temperature");
Serial.println(temp);
response.close(); // Free the resource
Updating an object is very similar to creating one. Assuming you have saved the object and have the objectId
, can do the following:
ParseObjectUpdate update;
update.setClassName("Temperature");
update.setObjectId("xWMyZ4YEGZ");
update.add("temperature", 100);
update.send();
To delete an object from the cloud:
ParseObjectDelete del;
del.setClassName("Temperature");
del.setObjectId("xWMyZ4YEGZ");
del.send();
So far we’ve used values with type string
, double
, and bool
. The Parse Arduino SDK also supports GeoPoint
s (latitude and longitude). In addition, you can set values on objects via JSON and be able to represent Arrays, Objects, Dates, and more. Read more about representing these types as JSON in the REST API guide.
Overall, the following types are allowed for each field in your object:
string
integer
, double
bool
JSON Array
JSON Object
JSON Date
ParseFile
ParseObject
ParseRelation
The type JSON Object
simply denotes that each value can be composed of nested objects that are JSON-encodable. Keys including the characters $
or .
, along with the key __type
key, are reserved for the framework to handle additional types, so don’t use those yourself.
Some examples:
ParseObjectCreate create;
create.setClassName("TestObject");
create.add("number", 42.0);
create.add("foo", "bar");
create.addGeoPoint("location", 40.0, -30.0);
create.addJSONValue("dateField", "{ \"__type\": \"Date\", \"iso\": \"2022-01-01T12:23:45.678Z\" }"); create.addJSONValue("arrayField", "[ 30, \"string\" ]");
create.addJSONValue("objectField", "{ \"number\": 30, \"string\": \"baz\" }");
create.addJSONValue("emptyField", "null");
create.send();
We do not recommend storing large pieces of binary data like images or documents in a ParseObject
. We recommend you use ParseFile
s to store images, documents, and other types of files. You can do so by instantiating a ParseFile
object and setting it on a field. See the Files section in the REST documentation for more details.
For more information about how Parse handles data, check out our documentation on Data.
We’ve already seen how a ParseObjectGet
with an objectId
can retrieve a single object from Parse. There are many other ways to retrieve data with ParseQuery
– you can retrieve many objects at once, put conditions on the objects you wish to retrieve, and more.
The general pattern is to create a ParseQuery
, put conditions on it, and then retrieve objects from the response. For example, to retrieve temperature data at a particular temperature, use thewhereEqualToInt
function to constrain the value for a key.
ParseQuery query;
query.setClassName("Temperature");
query.whereEqualTo("temperature", 100.0);
ParseResponse response = query.send();
int countOfResults = response.count();
Serial.println(countOfResults);
while(response.nextObject()) {
Serial.println(response.getJSONBody());
Serial.println(response.getDouble("temperature"));
Serial.println(response.getString("createdAt"));
}
response.close(); // Free the resource
Your query should have at least one constraint. There are several ways to put constraints on the objects found by a query. You can filter out objects with a particular key-value pair with:
query.whereNotEqualTo("toaster", "foo");
You can give multiple constraints, and objects will only be in the results if they match all of the constraints. In other words, it’s like an AND of constraints.
query.whereEqualTo("leverDown", true);
query.whereEqualTo("temperature", 100.0);
You can limit the number of results by setting a limit. By default, results are limited to 100. In the old Parse hosted backend, the maximum limit was 1,000, but Parse Server removed that constraint:
query.setLimit(10);
You can skip the first results by setting skip
. In the old Parse hosted backend, the maximum skip value was 10,000, but Parse Server removed that constraint. This can be useful for pagination:
query.setSkip(10);
For sortable types like numbers and strings, you can control the order in which results are returned:
// Sorts the results in ascending order by the temperature field
query.orderBy("temperature");
// Sorts the results in descending order by the temperature field
query.orderBy("-temperature");
You can sort by multiple keys as follows:
query.orderBy("temperature,name");
For sortable types, you can also use comparisons in queries:
// Restricts to temperatures < 50
query.whereLessThan("temperature", 50.0);
// Restricts to temperatures > 50 query.
whereGreaterThan("temperature", 50.0);
// Restricts to temperatures >= 50 query.
whereGreaterThanOrEqualTo("temperature", 50.0);
You can restrict the fields returned by calling setKeys with a list of keys as a string. To retrieve objects that contain only the temperature field (and also special built-in fields such asobjectId
,createdAt
, andupdatedAt
):
query.setKeys("temperature");
This is useful if the object has fields which are not required by the device. Since the Arduino is a constrained environment, we recommend using a combination of setKeys
andsetLimit
to reduce processing and memory overhead.
Because the Arduino SDK was designed to minimize memory footprint, it doesn’t provide direct functions for all the Parse features that are present in the mobile SDKs. However, it does have the capability to call the REST API, which offers the full range of functionality.
For example, you could sign up a user from Arduino through a REST call:
ParseResponse response = Parse.sendRequest("POST", "/parse/users", "{\"username\":\"cooldude6\",\"password\":\"p_n7!-e8\"}", "");
In this case, the response will contain the objectId of the created user, assuming it was created successfully.
Head on over to the REST API documentation to discover what’s possible. For each code sample, you can switch it to cURL to see what the endpoint and payload would look like.
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.
The Arduino SDK does not provide methods to directly sign in as a user. If you want to have the Arduino device act on behalf of a user, you will need to create a Session through a companion app or another Parse SDK and pass a Restricted Session to the device. You can read more about users in our REST API or one of our other SDK guides.
Once you have created a Restricted Session via the companion app or through the REST API, you can send the Session Token to the Arduino using push notifications, BLE, or some other appropriate method. After that, you can set it:
Parse.setSessionToken("r:olqZkbv8fefVFNjWegyIXIggd");
From then on, the device will act on behalf of the user.
User
SecurityParse uses ACLs to make sure objects can be accessed by users who are authorized with access. When creating objects, you can set the ACL field of the object to restrict access to a set of users. Read about this and more in the REST API documentation.
Using Push Notifications, you’ll be able to send realtime notifications to your Arduino. 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 for the Arduino. You can generate an installation ID (random lowercase UUID) elsewhere (e.g. phone), send that to your arduino during initial provisioning, then set the installation ID on the arduino:
// In this example, we associate this device with a pre-generated installation ID
Parse.setInstallationId("ab946c14-757a-4448-8b77-69704b01bb7b");
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 arduino will automatically create an Installation object with this installation ID.
If you do not pass in an installation ID, theParseClient
will automatically generate an installation ID for you, and create an Installation object with it upon the first request sent to Parse.
You can retrieve your installation ID with thegetInstallationId
function:
String installationId = Parse.getInstallationId();
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 Arduino 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.To subscribe to push notifications, make the following call in yoursetup
function:
Parse.startPushService();
Then, in your loop
function:
if (Parse.pushAvailable()) {
ParsePush push = Parse.nextPush();
// Print whole JSON body
String message = push.getJSONBody();
Serial.print("New push message size: ");
Serial.println(message.length());
Serial.print("New push message content: ");
Serial.println(message);
// Do something with the push
// IMPORTANT, close your push message
push.close();
}
There are many ways to send a push notification. It’s possible to send from the Arduino SDK via a call to the REST API, but you’ll most likely be sending from another environment. Read here for more information on how to send pushes.
Some examples:
ParseTrackEvent trackEvent;
trackEvent.setEventName("ButtonPress");
trackEvent.send();
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. We provide a command-line tool to help you deploy your Cloud Code. 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:
ParseCloudFunction cloudFunction;
cloudFunction.setFunctionName("hello");
cloudFunction.add("value", "echo from hello");
ParseResponse response = cloudFunction.send();
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.