Twitter – PSP (java) – Arduino (Peggy2)
December 12, 2009 2 Comments
Pictures of the final installation, WORKING !
… 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
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.
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:
Pingback: Lightweight software UART -> custom serial « Robotics / Electronics / Physical Computing
Pingback: IrDA Enabling a PC or Microcontroller « Robotics / Electronics / Computer Science