Hitchhikers Guide to the Android Universe
Dear Sparksters, and anyone interested in building your own Android application. Since I had the problem that I have not that much knowledge about Java/Android/JSON it was a hard trial-n-error process for myself. With this tutorial/example I would like to make your way a bit easier than mine was. Hope you like it, feel free to test it and report errors to this thread. I will update it asap.
1. The Spark Core Code
Fire up your browser and point it to the Spark Cloud IDE and “Create a new app” there. Name it as you like.
include "application.h"
void setup(){
}
void loop(){
char foo[64];
unsigned bar = "Metasyntactic Variable";
sprintf(foo, "{\"Foobar: \"}", bar);
Spark.publish("baz",foo);
}
In simple terms we put “{\”Foobar: \” \”Metasyntactic Variable”\ } into foo and send it to the spark-cloud api. At a closer look we use sprintf to compose a text string into a C variable:
Spark.publish() is used to send the data to the Spark Cloud via the API. There is a very good tutorial out there written by @bko It is highly recommended that you first try this one it is HTML based with JavaScript. It helped me a lot to get this to work.
Sadly we are not finished yet, we need a little bit more for this to run. We need to add the post example, and a little bit of cleaning up our code.
/*
* _________ .__
* \_ ___ \_______|__| _____ __________ ____
* / \ \/\_ __ \ |/ \ / ___/ _ \ / \
* \ \____| | \/ | | | \\___ ( <_> ) | \
* \______ /|__| |__|__|_| /____ >____/|___| /
* \/ \/ \/ \/
* Project: CrimsonCTRL
* Spark Core
*
* Author : CrimsonClyde
* E-Mail : clyde_at_darkpack.net
* Use at your own risk!
*/
void setup(){
// Register the post function
Spark.function("POST", postExample);
// Pin setup
pinMode(D7, OUTPUT);
digitalWrite(D7, LOW);
}
void loop(){
getExample();
delay(2000);
}
// Hitchikers Guide to Android Universe - GET function example
void getExample(void) {
char foo[64];
char bar[] = "Metasyntactic Variable";
sprintf(foo, "{\"Foobar: %s\"}", bar);
Spark.publish("baz",foo);
}
// Hitchikers Guide to Android Universe - POST function example
int postExample(String command) {
int state = 0;
//find out the pin number and convert the ascii to integer
int pinNumber = (command.charAt(1) - '0') - 1;
//Sanity check to see if the pin numbers are within limits
if (pinNumber != 7 ) return -1;
// find out the state of the led
if(command.substring(7,7) == "HIGH") state = 1;
else if(command.substring(7,6) == "LOW") state = 0;
else return -1;
// write to the appropriate pin
digitalWrite(D7, state);
return 1;
}
Woah that is much code! Keep calm, this seems to be more than it is right now. First I have created a function for the GET example to keep the main loop clean. Second I have added the function for the POST example. With that one we are able to set a Pin to HIGH or to LOW based on what we send with our application. Note the sanity check? I use PIN d7 in this example because of the build in LED into the Spark Core. If you need more than one PIN than you can use something like if (pinNumber <2 || pinNumber > 4) { … }.
Test? Of course we should test if everything works.
First Test check if our Function “POST” is registered correctly. Point your Browser to https://api.spark.io/v1/devices/YOURCOREID/?access_token=YOURACCESSTOKEN and you see something like this as output:
// 20150114121812
// https://api.spark.io/v1/devices/YOURCOREID/?access_token=YOURACCESSTOKEN
{
"id": "YOURCOREID",
"name": "YOURCORENAME",
"connected": true,
"variables": {
},
"functions": [
"POST"
],
"cc3000_patch_version": "1.29"
}
YES, we get closer, now if you are lucky and use a Unix, Linux or OSX as your operating system simple open up a terminal and use curl.
curl https://api.spark.io/v1/devices/<<CoreID>>/ -d access_token=<<accessToken>> -d params=d3,LOW
This should set your PIN d7 to HIGH and the build in LED (blue) should now light up. If you have Windows you can use Chrome Extension such as DHC Rest Client.
Request: HTTPS://api.spark.io/v1/devices/COREID/POST/
Headers: (form) Content-Type: application/x-www-form-urlencoded
Body: (form)access_token(text)YOURACCESSTOKEN
Body: (form)params(text)d7,HIGH
Woohoooo we have now set up the basic requirements. Thumbs up!
2. IDE
I build my project with Android Studio 1.0.2, it is now stable and the recommended tool for building Android Apps. If you want to use Eclipse feel free, the code should work in both IDEs.
3. New Project
Configure your new project: Application Name: AndroidUniverse, Company Domain: to.guide.hitchhikers
Select the form factors: Phone and Tablet Minimum SDK 21 (since I use Lollipop with Material Design, if you want to use lower SDK Version you have to change the Theme in style.xml)
Add an activity to Mobile: Blank Activity
Choose options for your new file: Activity Name: MainActivity, Layout Name: activity_main, Title: MainActivity, Menu Ressource Name: menu_main
4. Permissions (AndroidManifest.xml)
Since we want to connect to the spark-cloud our application needs the permission to access the internet. Open up the AndroidManifest.xml file located under app->manifests-AndroidManifest.xml.
Replace the content with this one:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="hitchikers.guide.to.androiduniverse" >
// Permission to get the values from spark-cloud
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- Layout (activity_main.xml)
We need a layout with a TextView we can update with our example and a button to submit our parameters and switch on our LED. The Layout is located under app->res->layout->activity_layout.xml – same as before, open it and replace the complete content with this one:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MyCoreApp"
android:id="@+id/headerTextView"
android:textSize="30sp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wait... foobar you said?"
android:id="@+id/getTextView"
android:textSize="24sp"
android:layout_marginTop="123dp"
android:layout_below="@+id/headerTextView"
android:layout_centerHorizontal="true"
android:focusableInTouchMode="true"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get Example"
android:id="@+id/getButton"
android:layout_below="@+id/getTextView"
android:layout_centerHorizontal="true"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POST EXAMPLE"
android:id="@+id/button"
android:layout_alignParentBottom="true"
android:layout_alignEnd="@+id/getButton"/>
</RelativeLayout>
We have a TextView with default content “Wait….foobar you said?”
We have a button which we will later use for get content and update the TextView.
Last not least a button for the post example which when tapped should post a request to set the PIN d7 to HIGH and the Core built-in app should light on.
6. Data Class (data.java)
Right-click in the left panel “app” and choose New -> Java Class. Name it “data.java”. Open it and replace the content with this:
package hitchikers.guide.to.androiduniverse;
public class Data {
private String foobar = null;
public boolean isReady() {
return (foobar != null);
}
public Integer getFoobar() { return foobar; }
public void setFoobar(Integer foobar) { this.foobar = foobar; }
@Override
public String toString() {
return "Data [Foobar=" + foobar + "]";
}
}
7. Remove the ActionBar (styles.xml)
We do not need the ActionBar for our project, open up app->res->values->styles.xml and replace the content with this:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
8. Main Activity (MainActivity.class)
This is our master file, it is a bit messy I know but it can show you how the post and get commands differ from each other. You have a lot of log and toast messages all around which you can safely remove but they can come in quite handy. Don´t forget to enter your AccessToken and CoreID on lines: 52,172 and 173!
package hitchikers.guide.to.androiduniverse;
/**
* _________ .__
* \_ ___ \_______|__| _____ __________ ____
* / \ \/\_ __ \ |/ \ / ___/ _ \ / \
* \ \____| | \/ | | | \\___ ( <_> ) | \
* \______ /|__| |__|__|_| /____ >____/|___| /
* \/ \/ \/ \/
* Project: CrimsonCTRL
* File: ControlActivity.java
*
* The Hitchikers Guide to Android Universe
* Example GET/POST tutorial for the Spark Core.
* See https://spark.io
*
* Author: CrimsonClyde
* Do this at your own risk, do not give your CoreID
* nor your AccessToken to anyone!
*
* Line 61 enter your CoreID & AccessToken
* Line 173 + 174 enter your CoreID & AccessToken
* Retrieve it from https://spark.io/build
*/
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class MainActivity extends Activity {
// Set actvity name as debug tag
public static final String TAG = HttpsClient.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Declare and assign our buttons and text
final Button getButton = (Button) findViewById(R.id.getButton);
final Button postButton = (Button) findViewById(R.id.postButton);
View.OnClickListener getListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// Update the <> with your CoreID and your AccessToken from https://build.spark.io
Toast.makeText(MainActivity.this, "GET baz", Toast.LENGTH_SHORT).show();
new HttpsClient().execute("https://api.spark.io/v1/devices/YOURCOREID/events/?access_token=YOURACCESSTOKEN");
}
};
getButton.setOnClickListener(getListener);
View.OnClickListener postListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// Post d7,HIGH to spark core
Toast.makeText(MainActivity.this, "POST d7,HIGH", Toast.LENGTH_SHORT).show();
new PostClient().execute("HIGH");
}
};
postButton.setOnClickListener(postListener);
}
/*
* GET EXAMPLE
*/
class HttpsClient extends AsyncTask<String, Void, String> {
private Exception exception;
public String doInBackground(String... urls) {
try {
Log.d(TAG, "******************* Open Connection *****************************");
URL url = new URL(urls[0]);
Log.d(TAG, "Received URL: " + url);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
Log.d(TAG, "Con Status: " + con);
InputStream in = con.getInputStream();
Log.d(TAG, "GetInputStream: " + in);
Log.d(TAG, "******************* String Builder *****************************");
String line = null;
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
Data data = new Data();
while ((line = br.readLine()) != null) {
if (line.contains("event")) {
//do nothing since the event tag is of no interest
Log.d(TAG, "Failed fetching needed values.");
return null;
}
if (line.contains("data: ")) {
//convert to JSON (stripping the beginning "data: "
JSONObject jObject = new JSONObject(line.substring(6));
String json_data = (String) jObject.get("data");
//convert again
jObject = new JSONObject(json_data);
//reading photocell
if (jObject.has("baz")) {
data.setBaz(jObject.getString("baz"));
}
}
//check if we have all needed data
if (data.isReady()) {
//exit endless connection
Log.d(TAG, "******************* Data received *****************************");
Log.d(TAG, "data: " + data);
break;
}
}
// Creation of finalized containers for UI usage
final String gBaz = data.getBaz();
// To access the findViewById we need this to runOnUiThread
runOnUiThread(new Runnable(){
public void run() {
Log.d(TAG, "******************* Run UI Thread *****************************");
Log.d(TAG, "gFoobar: " + gBaz);
// Assign and declare
final TextView updateGetExample = (TextView) findViewById(R.id.getTextView);
// Update the TextViews
Log.d(TAG, "******************* Update TextView *************************");
updateGetExample.setText(gBaz);
}
});
// Closing the stream
Log.d(TAG, "******************* Stream closed, exiting ******************************");
br.close();
} catch (Exception e) {
this.exception = e;
return null;
}
return null; }
}
/*
* POST EXAMPLE
*/
// We must do this as a background task, elsewhere our app crashes
class PostClient extends AsyncTask<String, Void, String> {
public String doInBackground(String... IO) {
// Predefine variables
String io = new String(IO[0]);
URL url;
try {
// Stuff variables
url = new URL("https://api.spark.io/v1/devices/YOURCOREID/SCL/");
String param = "access_token=YOURACCESSTOKEN¶ms=d7,"+io;
Log.d(TAG, "param:" + param);
// Open a connection using HttpURLConnection
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setReadTimeout(7000);
con.setConnectTimeout(7000);
con.setDoOutput(true);
con.setDoInput(true);
con.setInstanceFollowRedirects(false);
con.setRequestMethod("POST");
con.setFixedLengthStreamingMode(param.getBytes().length);
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// Send
PrintWriter out = new PrintWriter(con.getOutputStream());
out.print(param);
out.close();
con.connect();
BufferedReader in = null;
if (con.getResponseCode() != 200) {
in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
Log.d(TAG, "!=200: " + in);
} else {
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
Log.d(TAG, "POST request send successful: " + in);
};
} catch (Exception e) {
Log.d(TAG, "Exception");
e.printStackTrace();
return null;
}
// Set null and we´e good to go
return null;
}
}
}
That´s it - fire up your app! Cheers clyde