Twitter – PSP (java) – Arduino (Peggy2)


Pictures of the final installation, WORKING !

Final installation, on the kitchen table...

… so it all started, after I managed to make my Peggy2 LED board work, and I was wondering what to do with it…?

What else more obvious than display Twitter messages on it?

Now here’s the high level “design”:

– Peggy2 board with an Arduino microcontroller on it, should receive data serially, either over wire or IR

– how to get the data from the internet ? I don’t want to use a ethernet shield or wireless one, and it turns out I’ve been for so long annoyed that I have a PSP lying around and not being used… So let’s use it, it has wireless and can be programmed in Java (J2ME) be using the PSPKVM (greaaaat job guys !)

So there are 2 problems:

1) program the PSP to connect to the internet and download tweets from there

2) communicate between the PSP and the Arduino on Peggy

1) PSP to twitter.com

initial screen

2nd screen where it displays the results as a list

And here's theJ2ME code:

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.ItemCommandListener;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;

public class Midlet extends MIDlet implements CommandListener, ItemCommandListener{
 private static final Command EXIT_CMD = new Command("Exit", Command.EXIT, 1);
 private static final Command BACK_CMD = new Command("Back", Command.BACK, 1);
 private static final Command SUBMIT_CMD = new Command("Submit", Command.ITEM, 2);
 private static final Command LIST_SEL_CMD = new Command("List Select", Command.ITEM, 2);

 private TextField mInputField = new TextField("Query:", null, 100, TextField.ANY);
 private StringItem mResultMessage = new StringItem("Result: ", null);
 private StringItem mSubmitButton = new StringItem("", "Submit", Item.BUTTON);

 private Display mDisplay = Display.getDisplay(this);
 private Form mForm = new Form("Bring back news from Twitter...");
 private List mResultList = new List("Results", List.IMPLICIT);

 public Midlet(){
 mDisplay.setCurrent(mForm);

 mForm.addCommand(EXIT_CMD);
 mForm.setCommandListener(this);

 mSubmitButton.setDefaultCommand(SUBMIT_CMD);
 mSubmitButton.setItemCommandListener(this);

 mForm.append(mInputField);
 mForm.append(mSubmitButton);
 mForm.append(mResultMessage);

 mResultList.setSelectCommand(LIST_SEL_CMD);
 mResultList.setCommandListener(this);
 mResultList.addCommand(BACK_CMD);
 }

 public void commandAction(Command c, Displayable s) {
 if (c == EXIT_CMD) {
 destroyApp(false);
 notifyDestroyed();
 }else if(c == SUBMIT_CMD){
 new Thread(){
 public void run(){
 try{
 String[] results = TwitterMeAPI.twitterSearch("danalexo", mInputField.getString());
 mResultList.deleteAll();
 for(int i=0, n=results.length; i<n; i++){
 mResultList.append(results[i], null);
 }
 mDisplay.setCurrent(mResultList);
 }catch(Exception e){
 mResultMessage.setText("ERROR: " + e.getMessage());
 }
 }
 }.start();
 }else if(c == BACK_CMD){
 mDisplay.setCurrent(mForm);
 }else{
 mDisplay.setCurrent(new Alert(null, "Command: " + c.getLabel() + " NOT supported.", null, AlertType.ERROR));
 }
 }

 public void commandAction(Command c, Item item) {
 commandAction(c, mForm);
 }

 public void startApp() {
 }
 public void pauseApp() {
 }
 public void destroyApp(boolean unconditional) {
 }
}
import com.twitterapime.model.MetadataSet;
import com.twitterapime.search.LimitExceededException;
import com.twitterapime.search.Query;
import com.twitterapime.search.QueryComposer;
import com.twitterapime.search.SearchDevice;
import com.twitterapime.search.Tweet;
import java.io.IOException;

public class TwitterMeAPI {
 static String[] twitterSearch(String from, String word) throws IOException, LimitExceededException{
 SearchDevice sd = SearchDevice.getInstance();
 Query q = QueryComposer.append(QueryComposer.from(from), QueryComposer.containAny(word));
 Tweet[] result = sd.searchTweets(q);

 String[] resultStr = new String[result.length];
 for(int i=0, n=result.length; i<n; i++){
 resultStr[i] = result[i].getString(MetadataSet.TWEET_CONTENT);
 }

 return resultStr;
 }
}

2) PSP to internet - version 2 - using PSPSDK

It turns out there's no support for neither the IR nor the serial port in the PSPKVM, so back to square 1 : learn to program the PSP in C using the PSPSDK toolchain... Here's the source code, that uses a JSON C++ library for parsing, and a EasyCURL library special for PSP.

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspctrl.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string>
#include <fstream>
#include <curl/curl.h>
#include "easyconnect/easyconnect.h"
#include "json/reader.h"
#include "json/writer.h"
#include "json/elements.h"

using namespace std;
using namespace json;

/* Define the module info section */
PSP_MODULE_INFO("Twitter to IR", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER);

/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
 sceKernelExitGame();
 return 0;
}

/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
 int cbid;
 cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
 sceKernelRegisterExitCallback(cbid);
 sceKernelSleepThreadCB();
 return 0;
}

/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
 int thid = 0;
 thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
 if (thid >= 0) {
 sceKernelStartThread(thid, 0, 0);
 }
 return thid;
}

/** MY CODE **/

#define printf pspDebugScreenPrintf

// the buttons pad
SceCtrlData mPad;

void checkForExit(){
 // Refresh the PSP Button States
 sceCtrlReadBufferPositive(&mPad, 1);
 // If Circle is pressed exit the Application
 if(mPad.Buttons & PSP_CTRL_CIRCLE){
 sceKernelExitGame( );
 }
}

SceUID mSceUID;

int IR_Init( ){
 return (mSceUID = sceIoOpen("irda0:", PSP_O_RDWR, 0));
}

void IR_Write(string data){
 string toSend;

 int csInt = 0;
 for(int j=0; j<data.length(); j++) csInt += data[j];
 char checkSum = (char)(csInt % 255);

 for(int i=0; i<10; i++){
 toSend.append("#"); // tell the micro controller the start !
 toSend.append(data);
 toSend.append(1, checkSum); // add the checkSum
 }
 toSend.append("#");
 const char* toSendC = toSend.c_str();

 time_t currTime = time(0);
 printf("%s  Try to write: %s 300 times (CheckSum: %c / %d)\n", ctime(&currTime), toSendC, checkSum, checkSum);
 for(int i=0; i<300; i++){
 // max 1024 can be written at once !
 size_t written = sceIoWrite(mSceUID, toSendC, toSend.length());
 if(written != toSend.length()){
 printf("%d  ERROR written %d instead of %d ! \n", i, written, toSend.length());
 sceKernelDelayThread(500000); // might be usefull to wait a little...
 }

 sceKernelDelayThread(200000);
 checkForExit();;
 }
}

#define FILE_NAME "tmp.tmp"

// keep writing to a file
size_t curlCallback(void *buffer, size_t size, size_t nmemb, void *stream) {
 FILE * pFile = fopen(FILE_NAME,"a");
 size_t sizecontent = fwrite(buffer, size, nmemb, pFile);
 fclose(pFile);
 return sizecontent;
}

string getMsgFromFile(){
 ifstream fileStream(FILE_NAME);
 if(!fileStream) printf("Can't open file: %s\n", FILE_NAME);

 Object elemRootFile;
 Reader::Read(elemRootFile, fileStream);
 fileStream.close();
 if(fileStream) printf("Can not close file: %s\n", FILE_NAME);
 string result = (String)elemRootFile["results"][0]["text"];
 return result;
}

string getTwitterMessage(){
 CURL *curl;
 CURLcode res;
 curl = curl_easy_init();
 if (curl) {
 //delete any previous data
 if(remove(FILE_NAME) != 0) printf("Can't delete file: %s\n", FILE_NAME);
 // setup CURL
 curl_easy_setopt(curl, CURLOPT_URL, "http://search.twitter.com/search.json?q=from%3Adanalexo");
 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallback);
 // run CURL
 res = curl_easy_perform(curl);
 // clean CURL
 curl_easy_cleanup(curl);

 if (res == CURLE_OK) {
 string tweetMsg = getMsgFromFile();
 printf("Message downloaded:  %s\n", tweetMsg.c_str());
 return tweetMsg;
 } else {
 printf("Something went wrong with libcurl: error %i\n", res);
 }
 }else{
 printf("Something went wrong, can't get CURL");
 }

 return string("ERR");
}

int main(void)
{
 SetupCallbacks();

 // internet connection
 EasyConnect connection;
 connection.InitGU();
 connection.Connect();
 connection.TermGU();

 pspDebugScreenInit( );
 printf("\nTwitter to IR Application\n");
 pspDebugScreenSetXY(0,3);

 // IR
 IR_Init( );

 if(connection.IsConnected()){
 int i=0;
 while(1){
 try{
 //IR_Write(getTwitterMessage());
 stringstream strStream;
 strStream << i;
 IR_Write(getTwitterMessage().append(strStream.str()));
 i++;

 // shall we exit now ?
 checkForExit();
 }catch (exception& e){
 printf("Exception !!!!  %s\n", e.what());
 }
 }
 }else{
 printf("NOT CONNECTED !  \n");
 }
}
</pre>
<pre>

3) PSP to Peggy2 - using IRdA

Now this actually became quite an important project in itself, here's the corresponding blog... Pretty usefull explanation of the differences between IRDA and TTL.

IRDA vs TTL serial protocols

And the source code on the Peggy2 board:   MAIN:

#include <Peggy2.h>
#include <PeggyWriter.h>

Peggy2 mPeggyFrame;
PeggyWriter mPeggyWriter;
PeggyScroller mPeggyScroller;

#define MSG_MAX_SIZE 100
char mDisplayText[MSG_MAX_SIZE];

#define UPDATES_FREQ 10000  //1min between updates
unsigned long mLastUpdate = millis();

void setup(){
 //Serial.begin(115200);

 // Call this once to init the hardware:
 mPeggyFrame.HardwareInit();

 mPeggyScroller.init(&mPeggyFrame, &mPeggyWriter, 10, "INIT    ");
}

void loop(){
 mPeggyFrame.RefreshAll(100);
 mPeggyScroller.scrollLeft();
 mPeggyFrame.RefreshAll(100);

 unsigned long currentTime = millis();
 if(currentTime - mLastUpdate > UPDATES_FREQ){
 // try 3 times only if there are issues
 char* strRead = 0;
 for(byte i=0; i<3 && strRead == 0; i++){
 strRead = readSoftSerial();
 if(strRead != 0) updateMsg(strRead);
 }

 mLastUpdate = currentTime;
 }
}

void updateMsg(char* strRead){
 for(byte i=0; i<(MSG_MAX_SIZE - 4); i++){
 if(strRead[i] != 0) {
 mDisplayText[i] = strRead[i];
 }else{
 mDisplayText[i] = ' ';mDisplayText[i+1] = ' ';mDisplayText[i+2] = ' ';mDisplayText[i+3] = ' ';
 mDisplayText[i+4] = 0;
 break;          
 }
 }

 mPeggyScroller.init(&mPeggyFrame, &mPeggyWriter, 10, mDisplayText);
}
Serial communication with the IRdA transciever, using the NEW Software Serial library:
#include <NewSoftSerial.h>

#define RX_PIN 15  // A1
#define TX_PIN 14 // A0

#define MSG_TAG '#'

NewSoftSerial mySerial(RX_PIN, TX_PIN);
char mData[MSG_MAX_SIZE];
byte mPos = 0;

#define MAX_WAIT 100 // 0.1sec

char* readSoftSerial(){
 mySerial.begin(9600);

 // reinit data
 for(byte i=0; i<mPos; i++) mData[i] = 0;
 mPos = 0;

 // wait for the beginning of data
 const unsigned long maxTime = millis() + MAX_WAIT;
 while(!mySerial.available()){
 if(millis() >= maxTime) return 0;
 delay(10);
 }

 // wait for beginning of message
 while(mySerial.available() && (mySerial.read() != MSG_TAG));

 char aChar;
 while(mySerial.available() && (mPos < MSG_MAX_SIZE)){
 aChar = mySerial.read();
 if(aChar == MSG_TAG) break;

 mData[mPos] = aChar;
 mPos++;
 }

 mySerial.end();

 if(mPos > 0){
 int cs = 0;
 for(byte i=0; i<mPos-1; i++) cs += mData[i];
 if(mData[mPos-1] != (char)(cs % 255)) return 0; //checkSum NOT right 

 // remove the checkSum from the string
 mData[mPos-1] = 0;
 return mData;
 }

 // nothing read!
 return 0;
}

ANNEX

PSP Charger plug "duplicator" so that both the PSP and th Peggy2 work on only 1 DC source:

2 Responses to Twitter – PSP (java) – Arduino (Peggy2)

  1. Pingback: Lightweight software UART -> custom serial « Robotics / Electronics / Physical Computing

  2. Pingback: IrDA Enabling a PC or Microcontroller « Robotics / Electronics / Computer Science

Leave a comment