Blockly

monkeyserna edited this page May 25, 2014 · 36 revisions

Blockly

Qué es Blockly

Blockly es un lengaje de programación gráfica desarrollado por Google. Está implementado en HTML, CSS y Javascript y se ejecuta sobre páginas web.

Blockly es un proyecto de código libre, por lo que todas las fuentes están disponibles para su uso y modificación. Este lenguaje todavía está en fase de desarrollo, por lo que todavía no existe una versión final. En su lugar, se ofrece el código fuente para que los desarrolladores puedan adaptar el trabajo realizado a las necesidades individuales que se deseen.

Dos de los proyectos más populares que emplean este lenguaje son el proyecto Hour of code y el MIT App Inventor.

El proyecto Hour of Code es una iniciativa sin ánimo de lucro que pretende fomentar el aprendizaje de programación mediante videojuegos basados en Blockly. El usuario debe programar mediante bloques una serie de acciones para alcanzar un objetivo y así superar el nivel.

MIT App Invertor es un proyecto realizado por la prestigiosa Universidad Massachusetts Institute of Technology que permite realizar aplicaciones para dispositivos móviles empleado bloques gráficos.

Blockly en el modo Code

El modo de programación gráfica del robot emplea el lenguaje Blockly, extendiendo su funcionalidad con bloques propios con los que poder controlar el robot.

Para ejecutar estos bloques, primero se tienen que traducir a Python, lenguaje en el que está escrito el servidor de comandos, encargado de realizar la ejecución de estos bloques. El servidor a su vez tiene definidas unas funciones auxiliares que son empleadas en algunas de las traducciones a Python de los bloques. Por ejemplo, para enviar un comando a Arduino el servidor tiene definida una función llamada sendToArduino. Esta función se emplea en la traducción de numerosos bloques.

Por tanto, para crear un bloque gráfico se deben definir dos objetos: la descripción del bloque, y la función de traducción a Python.

Descripción de un bloque gráfico

Este objeto define las propiedades de un bloque y el aspecto visual que tendrá. La sintaxis que se emplea es la siguiente:

Blockly.Blocks['nombre_bloque'] = {
    init: function() {
        // propiedades del bloque
    }
};

A continuación se describen las principales propiedades que se pueden emplear para la construcción de un bloque propio. Hay que tener en cuenta que las características visibles se mostrarán de izquierda a derecha por orden de definición. Es decir, si escribimos:

Propiedad Descripción
this.setTooltip('quick help'); Propiedad obligatoria. Muestra un mensaje breve que indica cómo usar el bloque. Puede contener una cadena vacía ''.
this.setHelpUrl('http://www.help.com');
Propiedad obligatoria. Enlace a la web de ayuda. Puede contener una cadena vacía ''.
this.setColour(NUM);
Define el color del bloque. El rango de colores está limitado a los valores dentro del [0,364] que indican la saturación.
this.appendValueInput("value_name")
   [.setCheck("TYPE")];
Recibe un valor de entrada con nombre "value_name". Opcionalmente, se puede restringir el tipo de dato de entrada.
this.appendDummyInput()
    .appendField("Text to show");
Escribe un mensaje.
this.appendDummyInput()
    .appendField(
     new Blockly.FieldDropdown([
      ["opt 1", "OPTION1"], 
      ["opt 2, "OPTION2"], 
     ]), "list_name");
Define la lista desplegable con nombre "list_name". Para cada opción se define la tupla ["valor a mostrar", "nombre"].
this.setInputsInline(true);
Los valores de entrada se mostrarán inline (dentro del bloque), en vez de ser conectados de forma externa.
this.setOutput(true[, "TYPE" ]);
Indica que el bloque devuelve un valor. Opcionalmente, se puede restringir el tipo de dato de salida. Es incompatible con la opciones "setPreviousStatement" y "setNextStatement" .
this.setPreviousStatement(true);
this.setNextStatement(true);
Indica que el bloque no devuelve valor, y que puede tener instrucciones precedentes y sucesoras. Es incompatible con la opción "setOutput".

Es posible asociar los datos de entrada y salida a un tipo de datos. Los tipos datos disponibles son:

  • "Boolean" Booleano.
  • "Number" Valor numérico.
  • "String" Cadena.
  • "Array" Lista.

También destacar la diferencia entre tener las entradas como externas, o inline:

El bloque de la izquierda muestra la entrada "inline" (tiene establecida la propiedad `this.setInputsInline(true)), mientras que el de la derecha lo hace de forma externa.

Por último, destacar que existen principalmente dos tipos de bloques:

  • Los que devuelven datos, que tienen un conector a la izquierda del bloque.
  • Los que no devuelven datos, que tienen conectores superior e inferior, para poder establecer una secuenta.

A continuación se muestra la descripción de un bloque que devuelve un valor numérico:

Blockly.Blocks['read_sonar'] = {
    init: function () {
        this.setHelpUrl('https://github.com/monkeyserna/charlie/wiki/');
        this.appendDummyInput()
            .appendField("Read distance (cm)");
        this.setInputsInline(true);
        this.setOutput(true, "Number");
        this.setTooltip('');
    }
};

Un ejemplo de bloque propio más complejo y que no devuelve valores es move_motor:

Blockly.Blocks['move_motor'] = {
    init: function () {
        this.setHelpUrl('https://github.com/monkeyserna/charlie/wiki/');
        this.appendDummyInput()
            .appendField("Set")
            .appendField(new Blockly.FieldDropdown([
                ["left", CMD_SET_LEFT.toString()],
                ["right", CMD_SET_RIGHT.toString()],
                ["both", CMD_BOTH_MOTORS.toString() ]
            ]),
                "motor")
            .appendField("motor speed at");
        this.appendValueInput("speed")
            .setCheck("Number");
        this.appendDummyInput()
            .appendField("(0-255)");
        this.setInputsInline(true);
        this.setPreviousStatement(true);
        this.setNextStatement(true);
        this.setTooltip('');
    }
};

Función de traducción a Python

Además de la descripción del bloque, se debe implementar un objeto que contenga la función de traducción a Python.

Existen principalmente dos tipos de bloques: los que devuelven valor y los que no. La función de traducción debe tener este dato en cuenta, ya que la forma de devolver la traducción es ligeramente distinta.

La sintaxis de la función de traducción es la siguiente:

Blockly.Python['nombre_bloque'] = function (block) {

    // Operaciones necesarias para la traducción
    //El valor devuelto es distinto en función de si la traducción devuelve un valor o no

    return res; 
};

Esta función por tanto devuelve la traducción a Python del bloque nombre_bloque.

Obtener valores

Los datos de entrada de los bloques pueden definirse mediante listas desplegables o mediante entradas.

Desde lista desplegable

Para recuperar el valor de un desplegable, se emplea la siguiente función:

drop_state = block.getFieldValue('state');

En este ejemplo la función getFieldValue recupera el valor escogido en la lista con nombre state y su valor se almacena en la variable drop_state.

Desde valor de entrada

Para recuperar un valor de entrada se emplea la función valueToCode:

var value_wait = Blockly.Python.valueToCode(block, 'timeD', Blockly.Python.ORDER_ATOMIC)

En ejemplo anterior, la función obtiene el valor definido en la descripción como timeD, e indica el orden de precedencia como atómico. La información relacionada con los órdenes de precedencia está disponible en la referencia de Blockly.

Valor devuelto

La función de traducción debe devolver la traducción del bloque a lenguaje Python. Sin embargo, la sintaxis es ligeramente distinta, dependiendo de si el bloque devuelve un valor o no.

Bloque que no devuelve valor

La función de traducción debe devolver una cadena de texto con la traducción a Python de la instrucción equivalente, seguido de un salto de línea \n.

Por ejemplo, la función de traducción a Python del bloque de control de los leds obtiene dos valores, y devuelve una cadena con la función que realiza la acción correspondiente:

Blockly.Python['led'] = function (block) {
    var led = Blockly.Python.valueToCode(block, 'ledN', Blockly.Python.ORDER_ATOMIC)
    var state = block.getFieldValue('state');
    return PYT_SEND + '(' + CMD_LED + ',' + led + ',' + state + ')\n';
};

En el fichero values.js están definidas las variables:

  • PYT_SEND = "sendToArduino"
  • CMD_LED = 12;

Si suponemos que:

  • led = 0
  • state = 0

La traducción a Python del bloque led que devuelve esta función será:

"sendToArduino(12,0,0)\n"
Traducción de un bloque que sí devuelve valor

Si el bloque devuelve un valor, la función de traducción a Python debe devolver una tupla con dos elementos: la cadena que contiene la traducción a Python (¡sin saltos de linea \n!) y el orden de precedencia de este valor.

Las llamadas a función llevan la precedencia Blockly.Python.ORDER_FUNCTION_CALL. Para más información acerca de órdenes de precedencia, consulta la referencia de Blockly.

Un ejemplo: El bloque read_sonar devuelve el valor de la lectura del sonar.

Blockly.Python['read_sonar'] = function (block) {
    return [PYT_SONAR + '()', Blockly.Python.ORDER_FUNCTION_CALL];
};
  • En el fichero values.js se define PYT_SONAR = "readSonar";.
  • Por otra parte, el orden de precedencia de las funciones se define como Blockly.Python.ORDER_FUNCTION_CALL = 2;

Por tanto, el valor devuelto por esta función de traducción será la tupa:

["readSonar()",2]

Añadir palabras reservadas

Con el objetivo de prevenir colisiones entre la ejecución de los scripts realizados en en el modo de programación gráfico y el código Python del servidor, Blockly permite declarar una serie palabras reservadas que impiden que una función de traducción declare una variable o función con un nombre que ya está siendo empleado por el código del servidor, o bien es una función nativa del lenguaje Python.

Por ejemplo, algunos bloques emplean la función auxiliar sendToArduino para enviar comandos a Arduino. Si definimos mediante bloques gráficos una función con el mismo nombre (es decir, sendToArduino), cuando se realice la traducción a Python, Blockly renombrará esta función añadiéndole un sufijo numérico que prevendrá la sobreescritura de la función original.

Si no hubiésemos incluido la palabra reservada sendToArduino, la función original sería sobreescrita.

Por tanto, mediante esta instrucción se definen todas las palabras reservadas:

//Add reserved word to prevent collisions with python server
Blockly.Python.addReservedWords("logging,SERVER_IP,WS_PORT,LEDPIN,SERVOPIN,ECHOPIN,TRIGPIN,SONARTIMEOUT,DUE_PORT,DUE_BAUDS,initSonar,readSonar,destructSonar,moveServo,sendToArduino,receiveFromArduino,executeScript,map,WebServer,WebSocket,self,parser,OptionParser,options,args,SimpleSSLWebSocketServer,SimpleWebSocketServer,close_sig_handler,server,sys,servo,serialArduino,signal");
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.