Create custom blocks

Introduction

This instruction is written for CEREBRA developers to better understand the use of the installed Blockly component and how to expand its functionality.

Blockly can be used to generate code to control pib with only basic knowledge about programming. It has several pre-built “blocks” to create simple programming logic, e.g. if-else or common math functions. But obviously it has no blocks to recreate pib actions like reading or changing the angle of a motor. Thus we, the developers, have to add them to the Blockly component first.

How to use Blockly

Before we create our own blocks, it is useful to understand how Blockly works. The main concept is very simple: Select logical blocks and put them together like a jigsaw puzzle to create a sequence of actions. There are two kinds of blocks: statement blocks and value blocks. Statement blocks describe logical statements or actions, while value blocks can be used for calculations or simple getters. In programmer jargon you could say value blocks have to return something, while statement blocks return void or nothing.

Statement blocks

Figure 1 shows an if-block as an example for a statement block. It has one input slot for a value block, where the user can add the condition, and one for another statement block for the main clause. Additionally, it has a gear icon in the upper left corner. Clicking on it enables the user to add additional input slots (see Figure 2). Not every statement block has a gear icon. Also, the number of input slots may vary between different kinds of statement blocks and there may be no input slots at all. Still, every statement block needs either one connection slot to its previous action or one to its next - or, more often, both. Otherwise it would not be possible to use this block in a sequence or inside another statement, e.g. the main clause of an if- or while-statement.

Figure 1: Example for statement blocks (if-block)
Figure 2: Add connections to an if-block

Value blocks

Figure 3 shows some value blocks. Every value block has exactly one output, symbolized by the loop on its left border, and, just like statement blocks, it may or may not have one or multiple connection slots for custom input, which can be external or inline.

Figure 3: Example for value blocks

Block input

Both statement and value blocks may have multiple input slots in any order and combination. Every input slot accepts either a statement block or a value block - never both. As pictured in Figure 3, there are external inputs, on the right border of a block, and inline inputs, which are inside a block. Only value blocks can be added as an inline input. External input slots which accept only statement blocks have noticeably bigger sockets than those which accept only value blocks (see Figure 1).

How to create custom blocks

Now you know how to put together a simple program in Blockly. Now it is time to customize the default Blockly installation by adding pib functions and variables.

Code Introduction

CEREBRA has an build-in Blockly application under the menu point “Program”. Following code files need to be modified to add custom blocks to it:

  • program.component.ts

  • blockly.ts

blockly.ts contains an XML string with the workspace structure. Here you may add new categories and sub-categories. You can also add blocks to this XML string, but it is easier and less error prone to add them inside program.component.ts. Following code initializes a Blockly workspace with a given XML string:

Blockly.inject("blocklyDiv", { toolbox: xmlString, });

All blocks to be used have to be defined before that call. A single block can be defined with:

Blockly.Blocks[blockType] = { init: function () { blockJson; }, }

It is also possible to define multiple blocks at once:

Blockly.defineBlocksWithJsonArray(blockJsonArray);

In order to be displayed a block has to be defined and added to the XML. Thus, it is not recommended to modify blockly.ts to add blocks. Instead, define them in program.component.ts and use the method addToCategory or addManyToCategory to add them to categoryMap. Before the workspace gets initialized, CEREBRA checks this map and adds new block tags to each found category tag inside the XML string. One block tag looks like this:

<block type="block_type"></block>

A block’s type name is unique. So, you cannot define the same type twice with different content. You can add the same block type to multiple categories, though. You can also add the same block type to the same category twice but please do not do this.

Categories do not need to be defined. Thus, add them to the XML string in blockly.ts:

<category id="category_id" name="CategoryA" colour="#000000"></category>

name is the displayed name inside the category tree.

colour is the colour of the rectangle left of the category and its background colour when it is selected. Keep in mind that the font colour of the selected category is white. Thus, do not choose too bright colours.

id is not needed by Blockly to be displayed. All pre-built categories do not have an id tag. Still, categoryMap uses category ids as keys to add block tags to the XML.

It is possible to add category tags inside category tags to create sub categories. Add the attribute expanded to parent categories. Otherwise, selecting their sub categories will not work unless collapsing the parent category first. Whether expanded is set to false or true does not matter.

Helper functions

A block can be represented by a json, e.g. the json of a value block could look like this:

{ "type": "get_x", "message0": "getX", "output": "Number", "colour": 10, "tooltip": "get value of x", "helpUrl": "" }

type is the unique block type.

message0 is a text displayed on the block.

output is the return type. “Number” means this block will return a number. You can use custom return types, e.g. class names or enumerations. Some input slots accept only one specific input type or a set of input types. Then the output type has to correspond to this input type. When output is null, the output type is any.

colour is the block’s colour (a number between 0 and 360).

tooltip will be displayed when the user hovers over this block.

A statement block would have previousStatement and/or nextStatement instead of output. Both of which would be null.

Some blocks could be very simple and similar to each other, e.g. get_x and get_y. Therefore, program.component.ts has already some methods to simplify their creation:

  • loadCustomGetterBlocks: Creates getter blocks using createGetter.

  • loadComponentBlocks: Using createComponent it creates blocks which symbolize a component of pib. It is meant to be more dynamic than a getter. Thus, later those components could be loaded from a server or user configurations.

  • loadCustomFunctionBlocks: Creates function blocks using createFunction. Those are statement blocks without an input or output. They only have a text and a more detailed tooltip to make it more clear what they are supposed to do.

  • loadCustomBlocks: This is used to load more complicated blocks to our Blockly component. Create a JSON-File (e.g. using the Blockly Developer Tool) and save it in /assets/custom-blocks/. The file name must be identical to its block’s type. Moreover, it is important to save it in assets. Otherwise the Angular client will not be able to load this file. Inside the function loadCustomBlocks add the block type, which is the file name without its file ending, to the array blockNames and add it to a category.

Blockly Developer Tools

Some pib actions are more complicated than a simple getter. Thus, we need more complicated blocks, which may have several different input slots. Blockly Developer Tool can help us to create such blocks (see Figure 4).

Figure 4: Blockly Developer Tool

Legend of Figure 4:

  1. Insert the block type here.

  2. Customize the block’s input.

  3. Select how to depict the input (external or inline).

  4. Select how the block should be connected to other blocks. If you select left output, the block will be a value block. If you select top, bottom or top+bottom connection, the block will be a statement block.

  5. Insert a description. It will be displayed when a user’s mouse is hovered over the block.

  6. help url is irrelevant for us.

  7. output type is only visible if you select left output under 4. Otherwise your block will have no output. Blocks with a top connection will have a top type and those with a bottom connection will have a bottom type. Per default all three types are set to any. To change it open the menu item Type, select a block and drag it to its slot. To configure a custom type, e.g. a pib class or enumeration, select the block other and type your custom type’s name in the text field.

  8. Select the block’s colour.

On the right side of the Developer Tools there is a preview of what the customized block will look like. Add predefined blocks to the green block in the middle. Those predefined blocks are already categorized:

  • Input: Configure which other blocks should be connected to your block. There are three types of input blocks:

    • value input: Creates an input slot for a value block.

    • statement input: Creates an input slot for a statement block.

    • dummy input: Does not create an input slot for other blocks but it is still possible to add fields to it.

  • Field: Field blocks can only be added to input blocks (value, statement and dummy input). They can be used to add static or variable input to your block. Furthermore, the text field is useful to add a short text to an input slot, see Figure 5. Otherwise it might be unclear what a custom block is supposed to do.

  • Type: Used to define input and output types.

  • Colour: Select a predefined colour block and drag it to the colour slot to change your block’s colour.

When you are ready creating your custom block, add it to CEREBRA’s assets folder. There are two ways to do it:

  • Creating the JSON file yourself: Under the preview there is a segment called Block Definition. Select JSON as format, create a file in the assets folder with the block’s type as its name and copy the block definition to it.

  • Using Blockly’s exporter: Save your block by clicking the save button and go to the Block Explorer tab. There select the block in the Block Selector. Under Export Settings check the checkbox Block Definition(s), select JSON format, insert a file name (<block’s type name>.json) and press the export button. Modify the file to remove the surrounding square brackets and save it to assets.

It is possible to have multiple blocks in one JSON file and to import it to Blockly together. But it was decided to have one file for each block because thus it is easier to remove and modify old blocks.

To learn more about Blockly Developer Tools and how to use it read the official documentation: https://developers.google.com/blockly/guides/create-custom-blocks/blockly-developer-tools

Figure 5: Creating a block with Blockly Developer Tools