Archive.png This article or section documents a feature not included in the current version of Scratch (2.0). It is only useful from a historical perspective.
SandCastleIcon.png This page has links to websites or programs not trusted by Scratch or hosted by Wikipedia. Remember to stay safe while using the Internet, as we can't guarantee the safety of other websites.


Document stub.png This article or section may not have content matching Scratch Wiki editing standards. Please improve it according to Scratch Wiki:Guidelines and Scratch Wiki:Editing Conventions. (April 2018)
Reason: Much of this is in 1st person (look for "we") and 2nd person (look for "you"). 2nd person will likely need the sentences rewording but 1st person is easier.
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 does not include information about how to run the ActionScript code that is written.
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 using Windows Vista or a similar operating system one 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 that 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 in bytes.

Like this:

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

Receiving Data From Scratch

To communicate with Scratch, It will be necessary to make use of the Socket class. So, 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 the socket

Now that there is a Socket to work with, one will need to add some event listeners with the socket's addEventListener() method. When the socket dispatches an event the user is 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, 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
{
  //This will just call another function to do the work
  getData()
}

Now, there are the functions to handle when the socket connects, closes, and throws an error. Now all there is to do is connect.

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

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

Remember the getData() function we called from the onDataIn() event handler? This is what is next to write.

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. That can be traced if one wants to, but instead, we'll write some code to see what Scratch is sending.

  • If a variable was updated, the program will get a message in this form:
sensor-update "variable name" value
  • If a broadcast was dispatched, this will be received:
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 allows one to 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 an update, you should see this happen in the output panel.

Doing Something With This Data

Now that you 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]>> ::control
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

Next look at how to 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 procedures. 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, users 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 left 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! You 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())
}