View on GitHub

Scratch Extensions

Extensions for Scratch 2.0

Download this project as a .zip file Download this project as a tar.gz file

Contents

  1. Introduction
  2. Editor Interface
  3. Writing Extensions for Scratch 2.0
    1. Adding Blocks
      1. Command blocks
      2. Command blocks that wait
      3. Reporter blocks
      4. Reporter blocks that wait
      5. Boolean blocks
      6. Hat blocks
    2. The Extension Descriptor
      1. Blocks
      2. Menus
      3. URL
    3. Hardware Support
      1. USB HID Support
      2. Serial Device Support
  4. Frequently Asked Questions

Introduction

Scratch extensions make it possible to connect Scratch projects with external hardware and information on the web through custom blocks. The sample extension code in this github repository shows how to pull weather information from openweathermap.org, get input from a USB joystick, and other examples that demonstrate the range of possibilities.

This document explains how members of the Scratch Developer Program can develop and test extensions using their Scratch accounts. Membership in the Scratch Developer Program is currently invite only, but will be open to new members soon. We’re also developing criteria and workflow for making polished and vetted Scratch extensions publicly available to all users on the Scratch website, which we plan to publish in 2015.

The UI, workflow, and protocols for developing extensions should be considered alpha - and subject to change as we continue to refine them and act on feedback from the developer community. Members of the Scratch developer’s program are invited to ask questions and give feedback in the Extension Developer’s discussion forum on the Scratch website. (This forum is currently only visible to members of the developer program.)

( For information about Scratch 2.0 HTTP extensions please see this page. In this document the word extension will only refer to Scratch 2.0 Javascript Extensions. )

Editor Interface

A demonstration of the interface and workflow can be viewed here.

Scratchers with access to the extension development UI will see new options in the Extension Library window and the extension drop-down menus. In the Extension Library there is a button called My Extensions which will load a list of the user’s own extensions. The first item in this list is an option for creating a new extension.

Extensions can be in three states while editing them. Before a new extension is first uploaded to the Scratch servers, the extension menu looks like this:

When editing an extension, the editor will load and watch (we call it ‘connecting’ to a file) a local file. This allows the editor to detect changes to the file and display a reload button when changes are available for loading. These are the options for an extension that has been saved to the server and is still connected to a local file:

Lastly, here are the options for an extension that has been loaded with a project but is not connected to a local file:

Writing Extensions for Scratch 2.0

Writing a Javascript extension for Scratch 2.0 starts with some boilerplate code, which looks like the following:

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
        ]
    };

    // Register the extension
    ScratchExtensions.register('Sample extension', descriptor, ext);
})({});

Adding Blocks

An extension may define a number of blocks, of different types (e.g. a command block, or a hat block, or a reporter block). Blocks can take in parameters.

Command blocks

To add a simple command block, there needs to be an entry in the descriptors.blocks list, and a corresponding function for it. The simplest block possible is shown below (it does nothing).

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    ext.my_first_block = function() {
        // Code that gets executed when the block is run
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            // Block type, block name, function name
            [' ', 'my first block', 'my_first_block'],
        ]
    };

    // Register the extension
    ScratchExtensions.register('My first extension', descriptor, ext);
})({});

Command blocks that wait

Sometimes it is necessary to have a command block that waits (e.g. if a block plays a sound, it may be a good idea to wait till the sound playback finishes). The sample extension below implements a “random wait” block to show how that can be done. Note the use of the console.log statement in the code - most Javascript methods, as well as jQuery methods will work fine in an extension.

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    // Functions for block with type 'w' will get a callback function as the 
    // final argument. This should be called to indicate that the block can
    // stop waiting.
    ext.wait_random = function(callback) {
        wait = Math.random();
        console.log('Waiting for ' + wait + ' seconds');
        window.setTimeout(function() {
            callback();
        }, wait*1000);
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            ['w', 'wait for random time', 'wait_random'],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Random wait extension', descriptor, ext);
})({});

Reporter blocks

Blocks can also return values, and they are called reporter blocks. The corresponding JavaScript function for a reporter block needs to return a value, as shown in the example below (note that this example also shows how to make blocks accept parameters).

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    ext.power = function(base, exponent) {
        return Math.pow(base, exponent);
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            // Block type, block name, function name, param1 default value, param2 default value
            ['r', '%n ^ %n', 'power', 2, 3],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Sample extension', descriptor, ext);
})({});

Reporter blocks that wait

One common use-case for reporter blocks is getting data from online web-services, where the blocks need to wait for the web-api call to complete. The following example shows how to fetch the current temperature of a city using an AJAX call to Open Weather Map API. Note that the block type is R instead of r (which is for a non-blocking reporter).

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    ext.get_temp = function(location, callback) {
        // Make an AJAX call to the Open Weather Maps API
        $.ajax({
              url: 'http://api.openweathermap.org/data/2.5/weather?q='+location+'&units=imperial',
              dataType: 'jsonp',
              success: function( weather_data ) {
                  // Got the data - parse it and return the temperature
                  temperature = weather_data['main']['temp'];
                  callback(temperature);
              }
        });
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            ['R', 'current temperature in city %s', 'get_temp', 'Boston, MA'],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Weather extension', descriptor, ext);
})({});

Boolean blocks

Blocks can also return true or false, and they are called boolean blocks. The corresponding JavaScript function for a boolean block needs to return a value, as shown in the example below. Note that a boolean block that does not return anything is false.

(function(ext) {
    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    ext.true = function() {
        return true;
    };

    ext.false = function() {
        return false;
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            // Block type, block name, function name, param1 default value, param2 default value
            ['b', 'true', 'true'],
            ['b', 'false', 'false'],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Sample extension', descriptor, ext);
})({});

Hat blocks

Hat blocks go on top of block stacks - examples of Scratch hat blocks include “when green flag clicked” or “when this sprite clicked”. To create a hat block through an extension, the block type needs to be set to h, as shown in the example below.

(function(ext) {
    var alarm_went_off = false; // This becomes true after the alarm goes off

    // Cleanup function when the extension is unloaded
    ext._shutdown = function() {};

    // Status reporting code
    // Use this to report missing hardware, plugin or unsupported browser
    ext._getStatus = function() {
        return {status: 2, msg: 'Ready'};
    };

    ext.set_alarm = function(time) {
       window.setTimeout(function() {
           alarm_went_off = true;
       }, time*1000);
    };

    ext.when_alarm = function() {
       // Reset alarm_went_off if it is true, and return true
       // otherwise, return false.
       if (alarm_went_off === true) {
           alarm_went_off = false;
           return true;
       }

       return false;
    };

    // Block and block menu descriptions
    var descriptor = {
        blocks: [
            ['', 'run alarm after %n seconds', 'set_alarm', '2'],
            ['h', 'when alarm goes off', 'when_alarm'],
        ]
    };

    // Register the extension
    ScratchExtensions.register('Alarm extension', descriptor, ext);
})({});

The Extension Descriptor

The extension descriptor can be used for more than just listing the blocks offered by your extension. Here is an example of a more complex descriptor:

var descriptor = {
    blocks: [
        ['w', 'turn motor on for %n secs',             'motorOnFor', 1],
        [' ', 'turn motor on',                         'allMotorsOn'],
        [' ', 'turn motor off',                        'allMotorsOff'],
        [' ', 'set motor power %n',                    'startMotorPower', 100],
        [' ', 'set motor direction %m.motorDirection', 'setMotorDirection', 'this way'],
        ['h', 'when distance %m.lessMore %n',          'whenDistance', '<', 20],
        ['h', 'when tilt %m.eNe %n',                   'whenTilt', '=', 1],
        ['r', 'distance',                              'getDistance'],
        ['r', 'tilt',                                  'getTilt']
    ],
    menus: {
        motorDirection: ['this way', 'that way', 'reverse'],
        lessMore: ['<', '>'],
        eNe: ['=','not =']
    },
    url: 'http://info.scratch.mit.edu/WeDo'
};

Blocks

The blocks property is an array of block definitions. Each block definition is an array of three or more items. The required items are: op code, formatted label, and method name. These may optionally be followed by default block argument values.

The full list of block types available to an extension is as follows. Note that any operation that will run for more than a few milliseconds or will wait for an external event should be run asynchronously, as described above in the Reporter blocks that wait and Command blocks that wait sections.

Op Code Meaning
' ' (space) Synchronous command
'w' Asynchronous command
'r' Synchronous reporter
'R' Asynchronous reporter
'b' Synchronous boolean
'c' C block (note that you can’t run the blocks inside)
'h' Hat block (synchronous, returns boolean, true = run stack)

Each block argument is identified by a % character and the character following it specifies the type. The types are: %n for number, %s for string, and %m for menu. Menus also identify which menu to use with a period and the name of the menu like this: %m.menuName.

The menus property is an object whose properties define the menus used by block arguments in the extension. The property name is the menu name as it is used in the block definition(s). The property value is an array of options to display in the menu dropdown interface in Scratch.

For example, the setMotorDirection block in the example above includes %m.motorDirection, which will cause it to display a menu with 'this way', 'that way', and 'reverse' as options.

URL

The url property refers to a web page which describes the extension. Ideally, this page would describe each block, give examples, and identify any hardware or software required to use the extension. If the user clicks the 'About [extension name]...' menu item within Scratch, this is the URL that will be opened.

Hardware Support

Scratch provides its own set of APIs in order to allow extensions to access certain types of hardware. Currently, Scratch extensions may access the following types of hardware:

Extensions that request hardware access are required to implement two additional methods on the extension instance: _deviceConnected() and _deviceRemoved(). Both methods receive a device instance. To use the integrated hardware functions of the Scratch Extension API you pass the hardware information in the registration call:

ScratchExtensions.register('Example Name', descriptor_object, ext_instance[, hardware_info]);

The _getStatus() method of your extension can be used to indicate whether your extension has successfully communicated with a hardware device. For example:

ext._getStatus = function() {
    if(!device) return {status: 1, msg: 'Device not connected'};
    return {status: 2, msg: 'Device connected'};
}

The value returned by _getStatus() corresponds to the color of the status ‘light’ in Scratch and indicates the general state of your extension. The msg property can be used to provide more specific information.

Value Color Meaning
0 red error
1 yellow not ready
2 green ready

The details of the hardware_info parameter and the _deviceConnected() and _deviceRemoved() methods are described below in sections specific to each type of device.

_API Note: The hardware API is still somewhat experimental and may change in the future. In particular, we will soon be making a change to methods that return hardware data, such as read() for HID devices: these methods will take a callback in instead of returning data directly. This change is necessary to improve compatibility and allow us to expand the variety of environments in which hardware extensions are available._

USB HID Support

An example HID device extension is available here. More information about the HID protocol is available here.

To let the extension system know that your extension is interested in USB HID devices, pass an object like this for the hardware_info parameter of the register() method:

var hid_info = {type: 'hid', vendor: 0x0694, product: 0x0003};
ScratchExtensions.register('Example', descriptor, ext, hid_info);

The vendor and product values indicate the USB vendor and product ID of the device your extension supports. These values are frequently expressed as four-digit hexadecimal values, as indicated with the 0x prefix in the JavaScript above.

If a device is connected with matching vendor and product IDs, Scratch will call the _deviceConnected() method on your extension and pass an object representing that device. Your _deviceConnected() method should keep track of the device object and set up communication as necessary for your needs. For example, this will start polling the device for new HID data every 20 milliseconds:

var poller = null;
ext._deviceConnected = function(dev) {
    if(device) return;

    device = dev;
    device.open();

    poller = setInterval(function() {
        rawData = device.read();
    }, 20);
};

Once a connection to your device is established, your extension may use the read() and write() methods on the device object to communicate with it. These methods use ArrayBuffer objects to contain data being sent or received.

Your extension will also be notified if a matching device is disconnected, allowing your extension a chance to stop communication:

ext._deviceRemoved = function(dev) {
    if(device != dev) return;
    if(poller) poller = clearInterval(poller);
    device = null;
};

Finally, your extension’s _shutdown() method will be executed when the extension itself is shut down. For example:

ext._shutdown = function() {
    if(poller) poller = clearInterval(poller);
    if(device) device.close();
    device = null;
}

Serial Device Support

An example serial device extension is available here.

To let the extension system know that your extension is interested in serial devices, pass an object like this for the hardware_info parameter of the register() method:

var serial_info = {type: 'serial'};
ScratchExtensions.register('Example', descriptor, ext, serial_info);

Your extension’s _deviceConnected() method will be called for each serial port present on the computer. Your extension is responsible for checking if a suitable device is attached to that port and continuing on to the next port if necessary. Do not assume that the first time Scratch calls your _deviceConnected() will correspond to your device’s serial port. The PicoBoard extension shows an example of how to deal with this situation: if no valid PicoBoard communication is received on a given port withing a particular timeout, the extension assumes that there is no PicoBoard on that port and continues scanning to the next port.

var potentialDevices = [];
ext._deviceConnected = function(dev) {
    potentialDevices.push(dev);

    if (!device) {
        tryNextDevice();
    }
}

To communicate with a given serial port, your extension should open it with whichever options are appropriate for your device. These parameters are based on Boost.Asio’s serial port options. For a PicoBoard:

device.open({ stopBits: 0, bitRate: 38400, ctsFlowControl: 0 });

The full set of options available for a serial port are as follows:

Option Default Valid values Description
bitRate 9600 Any valid baud rate Up to The bit (or baud) rate at which to communicate.
bufferSize 4096 Up to 8192 The maximum amount of data that can be received at a time.
ctsFlowControl 1 (software) 0 (none), 1 (software), 2 (hardware) The type of flow control to use.
dataBits 8 5, 6, 7, 8 The number of data bits per character.
parityBit 0 (none) 0 (none), 1 (odd), 2 (even) Whether and how to use the parity bit in each character.
stopBits 1 (1.5 bits) 0 (1 bit), 1 (1.5 bits), 2 (2 bits) The number of stop bits per character.

Once a connection to your device is established, your extension may use the send() method to send data to your device, and the set_receive_handler() method to register a function to handle received data. These methods use ArrayBuffer objects to contain data being sent or received.

Your extension will also be notified if a device is disconnected, allowing your extension a chance to stop communication:

ext._deviceRemoved = function(dev) {
    if(device != dev) return;
    if(poller) poller = clearInterval(poller);
    device = null;
};

Finally, your extension’s _shutdown() method will be executed when the extension itself is shut down. For example:

ext._shutdown = function() {
    if(poller) poller = clearInterval(poller);
    if(device) device.close();
    device = null;
}

Frequently Asked Questions

####What is the Scratch Developer Program? The Scratch Developer Program consists of software developers who are interested in new technical initiatives within the Scratch ecosystem. Members of the Scratch Developer Program gain early access to initiatives such as the Scratch Extensions project. Members also have access to the Scratch extension developer forum. ####How do I join the Scratch Developer Program? Membership in the Scratch Developer Program is currently invite only, but will be open to new members next year. This program is for programmers with knowledge of Javascript. ####What are Scratch Extensions? Scratch extensions make it possible for Scratch to interface with external hardware and information outside of the Scratch website through new blocks. Extensions are written in JavaScript for the online Scratch project editor. ####How do I create a Scratch extension? You can learn more about how to create a Scratch extension here: http://llk.github.io/scratch-extension-docs/ ####Why can’t I share projects that use unpublished extensions? Until we have an approval process in place to ensure that all extensions meet security and quality standards, it won’t be possible to “share” projects that use extensions on the Scratch website. You can download the project using the file menu, and send the .sb2 project file to others who can use the file menu to load it into the Scratch editor and try it out. ####How can I make my extension available to everyone who uses Scratch? We’re working on an approval process for publishing Scratch extensions. We’ll make an announcement on the Scratch Extensions developer forums when we’re ready to accept submissions. Keep in mind we’re likely to choose only one official extension per functionality (hardware, web API). Here are some of the criteria we’ll be using to decide whether or not to accept an extension:

####I’m having trouble creating my extension - where can I get help? Publish your code someplace publicly viewable, like Github, and then post a link to it in the Scratch extension developers forum asking other developers for help. ####How do I request new features or submit bugs?
We would love to hear your thoughts and suggestions. Submit them as issues against this repository.