From Test-Scratch-Wiki

ActionScript 3.0 (AS3) is a language developed by Adobe for use on the Flash platform. This tutorial teaches how to communicate to and from Scratch with AS3.


Note Note: This tutorial assumes that you have at least a basic knowledge of how to run the ActionScript code we write.
Note Note: Remote Sensor Connections is a feature only used in Scratch 1.4

Setup: The Scratch side

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 Windows Vista or a similar operating system you may have to confirm this action through a system dialog.

Setup: The ActionScript side

To write ActionScript a user needs an ActionScript editor. This tutorial will be using Flash CS4, however free alternatives like FlashDevelop are available that can accomplish the same thing.

Communicating

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. 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

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

  • After the zero bytes is the length of the string message to pass, given as a 32-bit big-Endian number (4 bytes).
  • Lastly, there is the string message, whose length is the length of the message size.

Like this:

[size][size][size][size][string message (size bytes long)]

Receiving Data From Scratch

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()
}

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!

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!
}

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. Next, we need to use the messages from Scratch. For this, Regular Expressions (RegExps) will be used. Basically RegExps define a certain pattern a string must follow, and can be checked against a string to extract data from it. We're using two RegExps 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)

Information on RegExps can be found here.

/* -- 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])
}

This is what this code does:

  • 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

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:

when gf clicked
forever
ask [URL?] and wait
if <not <(length of (answer)) = [0]>>
set [new url v] to (answer)

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.

Sending data to Scratch

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 is
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 preceders. Notice the three zero bytes and the length of the message all go before the message itself. It then pushes these into the socket. 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+'"');
}

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

AS3 to Scratch Example: Time

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:

when gf clicked
forever
say ([time v] sensor value)

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

Conclusion

The entire script can be copied 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())
}