/** * SCRATCH OSC BRIDGE. By Kasper Kamperman - 03-09-2011 * http://www.kasperkamperman.com * * based on the excellent controlP5 and oscP5 examples and libraries from Andreas Schlegel. * http://www.sojamo.de/libraries/controlP5/ * http://www.sojamo.de/libraries/oscP5/ * * Based on Categnary v1.1 by Chalkmarrow * http://sites.google.com/site/chalkmarrowfiles/ * more about scratch remote: * http://wiki.scratch.mit.edu/wiki/Remote_Sensors_Protocol * http://scratch.mit.edu/forums/viewtopic.php?id=9458&p=1 * * Thanks to Heinze Havinga for the Regex parsing * http://www.leftfootmedia.nl/ * * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * * Compile for OPENGL (uses less CPU power) with * Processing 0199 or higher and ControlP5 0.5.9 * http://www.sojamo.de/files/archive/controlP5_0.5.9.zip * * new in version 31-08-2011: parseScratch data runs in a separate thread. * drawloop runs at 10fps, which cuts down the CPU power to 10% (instead of 50%). **/ // default values final String version = "03-09-2011"; int defaultIncomingPort; // add port number to add a port directly int transmitPort = 9000; String transmitIP = "127.0.0.1"; String addressPrefix = "/scratch"; boolean isAllData = false; import processing.net.*; // Processing network library import processing.opengl.*; import oscP5.*; import netP5.*; // netP5 library (included with oscP5) import controlP5.*; import java.net.InetAddress; import java.util.concurrent.ConcurrentHashMap; // http://www.codercorp.com/blog/java/why-concurrenthashmap-is-better-than-hashtable-and-just-as-good-hashmap.html // http://www.javaperformancetuning.com/articles/fastfail.shtml // get IP address from this computer InetAddress inet; String myIP; // store oscP5 objects HashMap oscP5Objects; // variabels to display incoming data ArrayList monitorList1; ArrayList monitorList2; int monitorListLength1 = 8; int monitorListLength2 = 8; String monitor1; String monitor2; String typetag; char tag; String monitorDisplay1; String monitorDisplay2; // variabels to filter incoming data HashMap oscaddressesHash; ArrayList oscaddressesList; int oscaddressCounter = 0; /// sending from Scratch NetAddress transmitaddress; //HashMap dataToScratch; ConcurrentHashMap sensorDataToScratch; ConcurrentHashMap broadcastDataToScratch; String sensorStringToScratch; String broadcastStringToScratch; Boolean sensorToScratch; Boolean broadcastToScratch; boolean scratchReady = false; Iterator iter; long reconnectMillis; long timeoutMillis; Client scratchClient; SimpleThread parseThread; // ControlP5 interface ControlP5 controlP5; Textarea mTextarea, mTextarea2; Textlabel ipTextlabel, infoTextlabel, text1, text2, text3, text4, text5; ListBox list1, list2, list3, list4; Textfield textInput, textInput2, textInput3, textInput4; Button button, button2, button3, button4; RadioButton radio; void setup() { size(800,515,OPENGL); // remove OPENGL to run in normal mode frameRate(10); try { inet = InetAddress.getLocalHost(); myIP = inet.getHostAddress(); } catch (Exception e) { e.printStackTrace(); myIP = "couldn't get IP"; } oscP5Objects = new HashMap(); monitorList1 = new ArrayList(); monitorList2 = new ArrayList(); oscaddressesHash = new HashMap(); oscaddressesList = new ArrayList(); sensorDataToScratch = new ConcurrentHashMap(); broadcastDataToScratch = new ConcurrentHashMap(); parseThread = new SimpleThread(20,"parseThread"); // transmit OSC controlP5 = new ControlP5(this); controlP5.setColorActive(0xff84becf); ipTextlabel = controlP5.addTextlabel("ipTextlabel","IP address: "+myIP+" ",24,24); infoTextlabel = controlP5.addTextlabel("info label","SCRATCH OSC BRIDGE, version " +version+ " by kasperkamperman.com ",24,495); list1 = controlP5.addListBox("list1",20,50,170,65); list1.setLabel("now listening to OSC ports:"); list2 = controlP5.addListBox("list2",20,140,170,65); list2.setLabel("list with common OSC ports"); text1 = controlP5.addTextlabel("text1","click on item to stop listening",24,110); text2 = controlP5.addTextlabel("text2","click on item to start listening",24,200); // add some common port numbers if(defaultIncomingPort != 0) list2.addItem(str(defaultIncomingPort),defaultIncomingPort); list2.addItem(str(8000),8000); list2.addItem(str(3333),3333); list2.addItem(str(8338),8338); // field to add a custom port number textInput = controlP5.addTextfield("textInput",20,220,170,20); textInput.setLabel(""); textInput.setAutoClear(true); // button to add the port number to list2 button = controlP5.addButton("button",1,20,250,60,16); button.setLabel("add Port"); // text area for displaying incoming OSC data (monitor) mTextarea = controlP5.addTextarea( "textarea", "", 210,20,570,100); mTextarea.setLineHeight(12); mTextarea.hideScrollbar(); mTextarea.setColorBackground(0xff222222); list3 = controlP5.addListBox("list3",210,140,275,130); list3.setLabel("received OSC addresses:"); list4 = controlP5.addListBox("list4",505,140,275,130); //430 list4.setLabel("send these OSC addresses to Scratch:"); text3 = controlP5.addTextlabel("text3","Click on item to pass data at this address to Scratch.",214,270); text4 = controlP5.addTextlabel("text4","Click on item to stop sending this address to Scratch.",509,270); mTextarea2 = controlP5.addTextarea( "textarea2", "", 210,310,570,100); mTextarea2.setLineHeight(12); //mTextarea2.valueLabel().setFont(ControlP5.grixel); //mTextarea.showScrollbar(); mTextarea2.hideScrollbar(); mTextarea2.setColorBackground(0xff222222); textInput2 = controlP5.addTextfield("textInput2",20,310,170,20); textInput2.setLabel(""); textInput2.setAutoClear(false); textInput2.setText(transmitIP); button2 = controlP5.addButton("button2",1,20,340,100,16); button2.setLabel("set host ip-address"); textInput3 = controlP5.addTextfield("textInput3",20,365,170,20); textInput3.setLabel(""); textInput3.setAutoClear(false); textInput3.setText(str(transmitPort)); button3 = controlP5.addButton("button3",1,20,395,120,16); button3.setLabel("set outgoing Port"); textInput4 = controlP5.addTextfield("textInput4",20,420,170,20); textInput4.setLabel(""); textInput4.setAutoClear(false); textInput4.setText(addressPrefix); button4 = controlP5.addButton("button4",1,20,450,120,16); button4.setLabel("set OSC address prefix"); radio = controlP5.addRadioButton("radioButton",210,420); radio.setNoneSelectedAllowed(false); radio.setSize(20,20); radio.setSpacingRow(10); radio.addItem("send all Scratch variables as OSC data",1); radio.addItem("Send only Scratch variables starting with a slash as OSC data",0); if(isAllData) radio.activate("Send all Scratch variables as OSC data"); else radio.activate("Send only Scratch variables starting with a slash as OSC data"); // connect with Scratch connectScratch(); } void draw() { // draw some background rectangles background(197,240,251); fill(86,145,160); rect(10,10,780,280); fill(86,160,128); rect(10,300,780,180); fill(32); rect(20,20,170,15); // textlabel rect(10,490,780,15); // textlabel fill(175,213,223); rect(20,50,170,55); // list 1 rect(20,140,170,55); // list 2 rect(210,140,275,125); // list 3 rect(505,140,275,125); // list 4 if(scratchReady) { // make sure that Scratch doesn't get too much data per second. // take a break every 30 milliseconds. if(millis() > (timeoutMillis + 30)) { sensorStringToScratch = "sensor-update "; broadcastStringToScratch = "broadcast "; sensorToScratch = false; broadcastToScratch = false; // make sensorString iter = sensorDataToScratch.values().iterator(); while (iter.hasNext()) { sensorStringToScratch = sensorStringToScratch + (String) iter.next() + " "; // data is send to monitorList1 from the sendtoscratch function sensorToScratch = true; } sensorDataToScratch.clear(); if(sensorToScratch) sendDataToScratch(sensorStringToScratch); // make broadcastString iter = broadcastDataToScratch.values().iterator(); while (iter.hasNext()) { broadcastStringToScratch = broadcastStringToScratch + (String) iter.next() + " "; broadcastToScratch = true; } broadcastDataToScratch.clear(); if(broadcastToScratch) sendDataToScratch(broadcastStringToScratch); timeoutMillis = millis(); } } else { // try to reconnect every 5 seconds if(millis() > (reconnectMillis + 5000)) { connectScratch(); reconnectMillis = millis(); } } // empty the monitorDisplay string monitorDisplay1 = ""; monitorDisplay2 = ""; // fill string with data from the monitorList. for(int i = 0; i < monitorList1.size(); i++) { monitorDisplay1 = monitorDisplay1 + (String) monitorList1.get(i) + "\n"; } for(int i = 0; i < monitorList2.size(); i++) { monitorDisplay2 = monitorDisplay2 + (String) monitorList2.get(i) + "\n"; } // remove items from beginning of the monitor list when size is bigger than // set monitorListLength while(monitorList1.size()>monitorListLength1) { monitorList1.remove(0); } while(monitorList2.size()>monitorListLength2) { monitorList2.remove(0); } mTextarea.setText(monitorDisplay1); mTextarea2.setText(monitorDisplay2); // scroll down to see the newest data. //mTextarea.scroll(1); } // == ControlP5 events =========================================== void controlEvent(ControlEvent theEvent) { if (theEvent.isGroup()) { int index = int(theEvent.group().value()); if(theEvent.group().name() == "list1") { stopListeningToPort(index); } if(theEvent.group().name() == "list2") { startListeningToPort(index); } if(theEvent.group().name() == "list3") { // check if item is already filtered String s = (String) oscaddressesList.get(index); if((Integer)oscaddressesHash.get(s) == 0) { list4.addItem(s, index); oscaddressesHash.put(s,new Integer(1)); } } if(theEvent.group().name() == "list4") { String s = (String) oscaddressesList.get(index); list4.removeItem(s); oscaddressesHash.put(s,new Integer(0)); } if(theEvent.group().name()=="radioButton") { if(theEvent.group().value() == 1) { monitorList2.add((String) "- Sending all Scratch variables as OSC data"); isAllData = true; } else { monitorList2.add((String) "- Send only Scratch variables starting with a / as OSC data"); isAllData = false; } } //println(theEvent.group()); } } // == add new port to listening list ============ void button(int theValue) { // trigger the textInput function below with the textInput data textInput.submit(); } void textInput(String s) { s = trim(s); String[] m = match(s, "[^0-9]|[0-9]{6}"); if(m != null) { // print error in monitor monitorList1.add((String) "- Please enter a number between 1 - 65535 "); } else { if(int(s)>65535) { // print error in monitor monitorList1.add((String) "- Please enter a number between 1 - 65535 "); } else { // add the port number to list2. // first remove to prevent double entries // doesn't give an error when its not yet in the list. list2.removeItem(s); list2.addItem(s, int(s)); startListeningToPort(int(s)); } } } // == set transmit ip address ============ void button2() { textInput2.submit(); } void textInput2(String s) { s = trim(s); textInput2.setText(s); // add again to textInput, since string can also come from Scratch String[] m = match(s, "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b"); if(m==null) { monitorList2.add((String) "- Please enter a valid IP Address. Write as 255.255.255.255"); } else { transmitIP = s; setTransmitAdres(); } } // == set transmit port ============ void button3() { textInput3.submit(); } void textInput3(String s) { s = trim(s); textInput3.setText(s); // add again to textInput, since string can also come from Scratch String[] m = match(s, "[^0-9]|[0-9]{6}"); if(m != null) monitorList2.add((String) "- Please enter a number between 1 - 65535 "); else { if(int(s)>65535) monitorList2.add((String) "- Please enter a number between 1 - 65535 "); else transmitPort = int(s); } println(transmitPort); setTransmitAdres(); } // == set OSC address prefix ============ void button4() { textInput4.submit(); } void textInput4(String s) { // check if prefix starts with a '/' if not add it s=trim(s); textInput4.setText(s); // add again to textInput, since string can also come from Scratch if(s.charAt(0) == '/') addressPrefix = s; else { addressPrefix = "/"+s; } textInput4.setText(addressPrefix+" "); monitorList2.add((String) "- addressPrefix set to: "+addressPrefix); } void setTransmitAdres() { transmitaddress = new NetAddress(transmitIP,transmitPort); // seems if(transmitaddress.isvalid()) { monitorList2.add((String) "- Sending Scratch OSC data to IP-address: " + transmitIP + " and port: " + str(transmitPort)); } else { monitorList2.add((String) "- Cannot find IP-address: " + transmitIP + " in this network..."); } } public void startListeningToPort(int port) { // triggered when clicking item in list2. // check if we are not already listening by checking the Hashmap // add new OscListener object to the oscP5Objects HashMap if(!oscP5Objects.containsKey(port)) { println(oscP5Objects.size()); // check if HashMap size() < 10 if(oscP5Objects.size()<5) { MyOSCListener o = new MyOSCListener(port); oscP5Objects.put(port,o); // add item to list1 list1.addItem(str(port), port); monitorList1.add((String) "- Listening for OSC data at port: "+port); } else { monitorList1.add((String) "- Cannot listen to more than 5 ports"); } } else { monitorList1.add((String) "- Already listening to port: "+port); } } public void stopListeningToPort(int port) { // triggered when clicking item in list1 if(oscP5Objects.containsKey(port)) { // get the port from the HashMap MyOSCListener o = (MyOSCListener) oscP5Objects.get(port); o.stop(); // stop listening oscP5Objects.remove(port); // remove port from HashMap // remove item from list1 String item = Integer.toString(port); list1.removeItem(item); monitorList1.add((String) "- Stopped listening to port: "+port); } } public void buttonEmptyLists(int theValue) { list3.clear(); list4.clear(); oscaddressesHash.clear(); oscaddressesList.clear(); oscaddressCounter = 0; } void addOSCaddress(String s) { if (!oscaddressesHash.containsKey(s)) { // Hash used to check if the adres already was used before. //println("not in list yet: "+s+" "+oscaddressCounter); oscaddressesHash.put(s,new Integer(0)); // show in list. list3.addItem(s,oscaddressCounter); oscaddressesList.add(new String(s)); // index will be the same as the oscaddressCounter oscaddressCounter++; } } void addOSCaddressByScratch(String s) { if (!oscaddressesHash.containsKey(s)) { // Hash used to check if the adres already was used before. //println("not in list yet: "+s+" "+oscaddressCounter); oscaddressesHash.put(s,new Integer(1)); // show in list. list3.addItem(s,oscaddressCounter); list4.addItem(s,oscaddressCounter); oscaddressesList.add(new String(s)); // index will be the same as the oscaddressCounter oscaddressCounter++; } else { if((Integer)oscaddressesHash.get(s) == 0) { //check the index in the oscaddressesList where the string is stored. for(int i = 0; i < oscaddressesList.size(); i++) { if(s.equals((String) oscaddressesList.get(i)) == true) { // add item to list4. list4.addItem(s, i); oscaddressesHash.put(s,new Integer(1)); } } } else monitorList2.add((String) "- Already sending address: "+s+" to Scratch"); } } void connectScratch() { try { // Scratch communicates at port 42001 if "remotes sensor connections" is enabled. // In Scratch right click "sensor value block" under "Sensing" to enable. scratchClient = new Client (this, "127.0.0.1", 42001); String ip = scratchClient.ip(); if(ip!=null) { monitorList1.add((String) "- Connected with Scratch"); if(defaultIncomingPort != 0) startListeningToPort(defaultIncomingPort); setTransmitAdres(); scratchReady = true; sendDataToScratch("broadcast OscBridgeConnected"); // start the Scratch thread for parsing data. parseThread.start(); } } catch (Exception e) { scratchReady = false; monitorList1.add((String) "- Scratch: not connnected. Start Scratch. Enable remote sensors and retry"); println("Connection failed"); } }