FANDOM


FlashLogo

Flash!

Actionscript 3 is a language developed by Adobe for use on the Flash platform. It is commonly used for building online animations and applications. The advantage of using Actionscript 3 over more lightweight, versatile scripting languages like Python or Ruby is that it is:
  • Much easier to write asynchronous event handlers
  • Easier to develop graphical applications around
  • Easier to deploy to end-users who may not be familiar with the tools required to run the scripts. Chalkmarrow's Snyff application, which makes it very easy to connect two computers running Scratch, is written in AS3.

Note: It is assumed that you have at least a basic knowledge of how to run the Actionscript code we write.

Setup: The Scratch side Edit

Scratch has long had a "sensors" block which allowed users to connect to a PicoBoard, but in version 1.3 a feature was added to let any application talk to Scratch. To enable this feature, a user must enter the Sensing Blocks palette, right click the sensors block and choose "enable remote sensor connections". The application will then open itself up to communication on port 42001. If you're using Vista or a similar dastardly operating system you may have to confirm this action through a system dialog.

Setup: The Actionscript side Edit

To write Actionscript a user needs an Actionscript editor. For this tutorial I'll be using Flash CS4, however free alternatives like FlashDevelop are available that can accomplish the same thing.

Communicating Edit

As mentioned before, Scratch allows other applications to communicate with it on port 42001. A port can be thought of as an outlet for an application that Scratch can send and receive data through. Here's what Scratch and other languages can send to each other:

  • Scratch will send a message through the socket whenever a global variable is updated or a broadcast is issued.
  • Another program can broadcast a message to Scratch, or can add custom values to the sensor block below the built-in ones.

However, another application cannot set Scratch global variables through the port. The Scratch Connections Wiki is a good resource for learning more about this. For the purposes of this tutorial, an overview of the Scratch Extension Protocol is given. This basically describes what an application needs to do to send messages Scratch will understand.

The Protocol Edit

All messages to Scratch must be sent to port 42001 in this format:

  • The message is preceded by three empty (zero) bytes
  • After the zero bytes is the length of the string message to pass
  • The string message

Like this:

[0][0][0][message size](string message)

Receiving Data From Scratch Edit

To communicate with Scratch, we'll need to make use of the Socket class. So let's create a new Socket and connect it to port 42001.

var host:String = "localhost" //This means we're connecting to our own computer (127.0.0.1)
var sock:Socket = new Socket() //This is our socket

Now that we have a Socket to work with, we need to add some event listeners with the socket's addEventListener() method. When the socket dispatches an event we're listening for, it will call a handler function we specify.

sock.addEventListener(Event.CONNECT,onConnect) //Called when the socket connects
sock.addEventListener(Event.CLOSE,onClose) //Called when the socket is closed
sock.addEventListener(IOErrorEvent.IO_ERROR,onError) //Called on a connection problem
sock.addEventListener(ProgressEvent.SOCKET_DATA,onDataIn) //Called when data is received

Now we write the event handlers for these listeners:

function onConnect(e:Event):void
{
  trace("Connected!")
}

function onClose(e:Event):void
{
  trace("Socket has been closed.")
}

function onError(e:IOErrorEvent):void
{
  trace("Oh no! Trouble connecting!")
}

function onDataIn(e:ProgressEvent):void
{
  //We'll just call another function to do our dirty work
  getData()
}

Okay, now we know when the socket connects, closes, and throws an error. Now all we need to do is connect!

sock.connect(host,42001) //Pass the host variable from above and 42001 to connect()

Test the movie. If you've done everything right and Scratch is open with remote sensor connections enabled you should see "Connected!" trace to the output panel after a few seconds. Now that we're connected, we can send and receive data with Scratch!


Okay, remember the getData() function we called from the onDataIn() event handler? We're going to write that now.

function getData():void
{
  var data:String=""
  while (sock.bytesAvailable) {
    data+=sock.readUTF();
  }
  trace(data)
  
  //More code here in a bit!
}

There! Everything Scratch sent is now in the variable named data. We can trace that if we want, but instead let's write some code to see what Scratch is trying to tell us.

  • If a variable was updated, we'll get a message in this form:
sensor-update "variable name" value
  • If a broadcast was dispatched, we'll see this:
broadcast "broadcast name"

To see this in action, test the movie again, then head over to Scratch and change some global variables or broadcast some things. The messages will show up in the output panel. Now this is all fine and dandy, but we want to be able to do something with this data! Unfortunately that means we're going to use some regular expressions. If you don't know what a regular expression (often abbreviated RegExp or RegEx), don't fret; neither does the vast majority of the human race. Basically they define a certain pattern a string must follow, and can be checked against a string to extract data from it. We're using two RegExp's here:

/broadcast\s\"(.*)"/ (This matches a string that follows the 'broadcast' pattern)

/sensor-update\s\"(.*?)\"\s\"(.*?)\"/ (This matches a string that follows the 'sensor-update' pattern)

If this is still confusing but you're curious about regular expressions, Regular-Expressions.info is an excellent tutorial resource. Otherwise, feel free to ignore (but add!) this next bit of code:

/* -- I GO WITHIN THE getData() FUNCTION! -- */

//We're still within the previous getData() function here. Place this code after 'trace(data)'
var broadcast_pattern:RegExp=/broadcast\s\"(.*)"/;
var sensor_pattern:RegExp=/sensor-update\s\"(.*?)\"\s\"(.*?)\"/;
var sensor_num_pattern:RegExp=/sensor-update\s\"(.*?)\"\s([-|\d|.]+)/;
var result:Object;
//See those parentheses in the regexes above? The values within those are actually stored
//in the 'result' object below at indices 1,2,etc.
result=broadcast_pattern.exec(data);
if (result != null) {
  //It's a broadcast! Call the broadcasted() function with the name of the broadcast
  broadcasted(result[1])
}
result=sensor_pattern.exec(data);
if (result != null) {
  //It's a sensor-update! Call the updated() function with the variable name and the new value
 updated(result[1],result[2])
}
result=sensor_num_pattern.exec(data)
if(result != null){
  //It's...the same as before, only Scratch passed a numerical value.
  updated(result[1],result[2])
}

If you're keeping score at home, you'll know exactly what this code does. Otherwise, know this:

  • If Scratch broadcasted something, it calls the broadcasted() function with the broadcast name.
  • If Scratch updated a variable, it calls the updated() function with the updated variable and the new value.

Now, of course, we have to write these functions:

function broadcasted(broadcast:String):void
{
  trace("Scratch broadcasted",broadcast)
}

function updated(variable:String,value:String):void
{
  trace("Scratch set",variable,"to",value)
}

Test the movie and start playing with Scratch! As you broadcast and update, you should see this happen in the output panel.


Doing Something With This Data Edit

Now that we can understand Scratch's messages, it would be nice to be able to do something with them. The following explains how to launch a browser window from Scratch!


First, for the Scratch side. Create a new project and add a script like this:

URLScript

This repeatedly asks for a URL. Now on the AS3 side we'll modify the updated() function to look like this:

function updated(variable:String,value:String):void
{
  trace("Scratch set",variable,"to",value)
  if(variable=="new url"){
    navigateToURL(new URLRequest(value))
  }
}

Now when the "new url" Scratch variable is updated, Flash will open a browser window at the specified URL. Cool, huh?

Sending data to Scratch Edit

Let's look at how you send data to Scratch. If you recall from above, all messages must be formatted like so:


[0][0][0][message size](string message)


Let's look at the (string message) part. The way we send these messages is identical to the way we receive them:

  • If we're updating a remote sensor, it'll look like this:
sensor-update "variable name" value
  • If we're broadcasting, it's
broadcast "broadcast name"

This is simple enough. Knowing how to format a message to Scratch, we can then write the following function:

function sendScratchMessage(s:String):void
{
  var bytes:ByteArray=new ByteArray();
  bytes[0]=0;
  bytes[1]=0;
  bytes[2]=0;
  bytes[3]=s.length;
  for (var i:Number=0; i<4; i++) {
    sock.writeByte(bytes[i]);
  }
  sock.writeMultiByte(s,"us-ascii");
  sock.flush();
}

Basically this takes whatever is in the string "s" and wraps it in a ByteArray with the appropriate preceeders. Notice the three zero bytes and the length of the message all go before the message itself. It then pushes these into the socket. But wait, there's more! With this function, we can write two more that make broadcasts and sensor updates a breeze:

function update(variable:String,value:*):void {
  if(!sock.connected) return
  value=String(value);
  sendScratchMessage('sensor-update "'+variable+'" "'+value+'"');
}

function broadcast(broadcast:String):void {
  if(!sock.connected) return
  sendScratchMessage('broadcast "'+broadcast+'"');
}

Voila! Now all we have to do is call these functions to send things through the socket.

AS3 to Scratch Example: Time Edit

Like the URL block, something Scratch users often request is a time block. Let's replicate that functionality. This bit of code will create a 'time' item in the sensor block dropdown.

var timer:Timer = new Timer(1000)
timer.addEventListener(TimerEvent.TIMER,everySecond)
timer.start()

function everySecond(e:TimerEvent):void
{
  var d:Date=new Date()
  update('time',d.toTimeString())
}

Now head over to Scratch and on a new sprite do something like this:

Scr-as3-2

And...that's it! We now have access to time functionality in Scratch (at least with this Flash file running)

Conclusion Edit

After reading this tutorial you should have an understanding of what it takes to send data between Flash and Scratch. You may also have picked up on a bit of general socket programming that you can apply to other projects!

If you're too lazy to read any of this, you can just copy the entire script into a Flash movie:

var sock:Socket = new Socket();
sock.addEventListener(ProgressEvent.SOCKET_DATA,onDataIn);
sock.addEventListener(Event.CONNECT,onConnect)
sock.addEventListener(Event.CLOSE,onClose)
sock.addEventListener(IOErrorEvent.IO_ERROR,onError)
sock.connect('localhost',42001)
function onDataIn(e:ProgressEvent):void
{
	getData();
}

function onConnect(e:Event):void
{
  trace("Connected!")
}

function onClose(e:Event):void
{
  trace("Socket has been closed.")
}

function onError(e:IOErrorEvent):void
{
  trace("Oh no! Trouble connecting!")
}

function getData():void
{
	var data:String="";
	while (sock.bytesAvailable)
	{
		data+=sock.readUTF();
	}
	trace(data)
	var broadcast_pattern:RegExp=/broadcast\s\"(.*)"/;
	var sensor_pattern:RegExp=/sensor-update\s\"(.*)\"\s\"(.*)\"/;
	var sensor_num_pattern:RegExp=/sensor-update\s\"(.*?)\"\s([-|\d|.]+)/;
	var result:Object;
	//See those parentheses in the regexes above? The values within those are actually stored
	//in the 'result' object below at indices 1,2,etc.
	result=broadcast_pattern.exec(data);
	if (result!=null)
	{
		//It's a broadcast! Call the broadcasted() function with the name of the broadcast
		broadcasted(result[1]);
	}
	result=sensor_pattern.exec(data);
	if (result!=null)
	{
		//It's a sensor-update! Call the updated() function with the variable name and the new value
		updated(result[1],result[2]);
	}
	result=sensor_num_pattern.exec(data)
	if(result != null){
		updated(result[1],result[2])
	}
}

function broadcasted(broadcast:String):void
{
  trace("Scratch broadcasted",broadcast)
}

function updated(variable:String,value:String):void
{
  trace("Scratch set",variable,"to",value)
  if(variable=="new url"){
	  navigateToURL(new URLRequest(value))
  }
}
function sendScratchMessage(s:String):void
{
  var bytes:ByteArray=new ByteArray();
  bytes[0]=0;
  bytes[1]=0;
  bytes[2]=0;
  bytes[3]=s.length;
  for (var i:Number=0; i<4; i++) {
    sock.writeByte(bytes[i]);
  }
  sock.writeMultiByte(s,"us-ascii");
  sock.flush();
}

function update(variable:String,value:*):void {
  if(!sock.connected) return
  value=String(value);
  sendScratchMessage('sensor-update "'+variable+'" "'+value+'"');
}

function broadcast(broadcast:String):void {
  if(!sock.connected) return
  sendScratchMessage('broadcast "'+broadcast+'"');
}

var timer:Timer=new Timer(1000)
timer.addEventListener(TimerEvent.TIMER,everySecond)
timer.start()

function everySecond(e:TimerEvent):void
{
	var d:Date=new Date()
	update('time',d.toTimeString())
}

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.