Files
clover/clover_blocks/www/blocks.js
Oleg Kalachev 2f6125ce54 Implement block programming (using Blockly) (#272)
* Clover Blockly: add first blocks set

* Adjust Blockly settings

* Fix get_position output type

* Add screenshot

* Rename readme.md to README.md

* Resize screenshot

* Add package.xml

* Little change

* Fixes

* Add python_compressed to blockly

* Implement some of the Clover blocks in Python

* Make Python indentation 4 spaces

* Fixes to Python blocks implementation

* Implement set_velocity block in Python

* Implement wait_arrival block in Python

* Fix indentation in Python implementation of blocks

* Fix

* Fix land_wait template

* Set reserved words in Python

* Change default frame_id to aruco_map in get_position block

* Fix

* Move blocks definitions to blocks.js

* Get rid of missing favicon error

* Simplify navigate

* Rearrange layout, add tabs

* Generate Python code

* Small style change

* -console.log

* Code style

* Use modules

* Move modules to the header

* Correct order for ROS definitions + generating "backend" code

* Fix rangefinder_distance block

* simple_offboard: commands to change only yaw and yaw rate

* Implement set_yaw block

* Start working on Blockly documentation

* Implement print block with a topic

* Unneeded code

* Little fixes

* Fix indentation

* Fixes

* Fix wait_arival, get_distance

* Implement running Blockly programs, implement prompt block, fixes

* Add land button

* Little change

* Fix reserved words + little fixes

* +x for main.py

* Simplify run button

* Auto-save and load workspace

* Make land button work

* Handle exceptions

* Minor change

* Add help URL for blocks

* Fix

* Implement arrived block

* Mark blockly and highlight.js as linguist-vendored

* Add forgotten CMakeLists.txt

* Add wait checkbox to set_yaw block

* Disable run button when disconnected

* Add message and service files

* Add some comments

* Add tooltip to some blocks

* Implement GPIO blocks

* Don’t latch print message to prevent duplication

* Prevent duplication prompts

* Add ROS init code to backend code anyways

* Make GPIO blocks color a constant

* Minor fix

* More correctly update blocks on input value changes

* Minor fixes

* Remove unneeded readonly attribute

* Add marker ID shadow blocks to toolbox

* Add lacking reserved words

* Fix frame id generation for complex marker id expressions

* Consider frame_id in set_yaw block

* Shorten ros module import

* Implement stop service

* Disable and enable run button correctly

* Don’t print KeyboardInterrupt exceptions

* Put notifications to notifications element

* Add 'running' mark

* Disable signal in backend python code

* Sleep a little bit to let rospy initialize publishers

* Remove accidental code

* Make ROS namespace and private namespace constants

* editorconfig-lint: don’t check Blockly code

* Use private namespace constant in Python generator

* Implement ~running topic to display current program status more robustly

* Make navigate tolerance and sleep time constants

* Make set_leds and and set_effect services proxies persistent

* Replace a number with constant

* Limit ~block topic publishing rate
Otherwise messages get queued making the frontend to freeze

* Improve internal documentation

* Append 'map' to frames list

* Return degrees in get_attitude block

* Move getting yaw in a separate block

* Improve block tooltips

* Add some more files to editorconfig-lint excludes

* Add get_yaw block to toolbox

* Implement get_time block

* Implement ~store and ~load services for storing user programs

* Set auto_arm only in take_off block

* Minor CSS fixes

* Make 'Python' tab textarea-like

* Implement saving and loading programs

* Adjust styles

* Retrieve only .xml files in load service

* Forgotten code

* Documentation on store and load services

* Add some examples

* Add blocks programming arg to launch file

* Update docs

* Add package’s dependencies

* Add dependency

* Add title to select

* Fix syntax

* Minor fix in docs

* Add forgotten roslib.js

* Run user program in the same process

* Use print function for print block in Python 2

* Add variables example

* Fix url

* Add functions example

* Fix set_servo block

* Fix gpio_read block

* Update blocks screenshot

* Update docs

* Update docs

* Fix set_effect block

* Minor fix in example

* Add setpoint block, remove set_velocity from toolbox

* Remove unused modules

* Unused variable

* Add English article skeleton

* Clarify backend node link error

* Remove unused variable

* Update documentation

* Fix link to documentation

* Add Blockly logo

* Update English article

* Add Blocks programming link to the main page

* Minor change

* Add catkin_install_python to CMakeLists.txt

* Make navigate tolerance and sleep time configurable

* Add minor todo

* Add blockly examples directory to editorconfig-lint excludes

* Rename main node to clover_blocks

* Add a warning to the old blocks programming article

* Fix editorconfig-lint exclude
2020-09-30 17:07:03 +03:00

565 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const COLOR_FLIGHT = 293;
const COLOR_STATE = 36;
const COLOR_LED = 143;
const COLOR_GPIO = 200;
const DOCS_URL = 'https://clover.coex.tech/en/blocks.html';
var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]];
function considerFrameId(e) {
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
var frameId = this.getFieldValue('FRAME_ID');
// set appropriate coordinates labels
if (this.getInput('X')) { // block has x-y-z fields
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK') {
this.getInput('X').fieldRow[0].setValue('forward');
this.getInput('Y').fieldRow[0].setValue('left');
this.getInput('Z').fieldRow[0].setValue('up');
} else {
this.getInput('X').fieldRow[0].setValue('x');
this.getInput('Y').fieldRow[0].setValue('y');
this.getInput('Z').fieldRow[0].setValue('z');
}
}
if (this.getInput('ID')) { // block has marker id field
this.getInput('ID').setVisible(frameId == 'ARUCO'); // toggle id field
}
this.render();
}
function updateSetpointBlock(e) {
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
considerFrameId.apply(this, arguments);
var type = this.getFieldValue('TYPE');
var velocity = type == 'VELOCITY';
var attitude = type == 'ATTITUDE' || type == 'RATES';
this.getInput('VX').setVisible(velocity);
this.getInput('VY').setVisible(velocity);
this.getInput('VZ').setVisible(velocity);
this.getInput('YAW').setVisible(attitude);
this.getInput('PITCH').setVisible(attitude);
this.getInput('ROLL').setVisible(attitude);
this.getInput('THRUST').setVisible(attitude);
this.getInput('RELATIVE_TO').setVisible(type != 'RATES');
if (type == 'RATES') {
this.getInput('ID').setVisible(false);
}
this.render();
}
Blockly.Blocks['navigate'] = {
init: function () {
this.appendDummyInput()
.appendField("navigate to point");
this.appendValueInput("X")
.setCheck("Number")
.appendField("forward");
this.appendValueInput("Y")
.setCheck("Number")
.appendField("left");
this.appendValueInput("Z")
.setCheck("Number")
.appendField("up");
this.appendDummyInput()
.appendField("relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false)
this.appendValueInput("SPEED")
.setCheck("Number")
.appendField("with speed");
this.appendDummyInput()
.appendField("wait")
.appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT");
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Navigate to the specified point, coordinates are in meters.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(considerFrameId);
}
};
Blockly.Blocks['set_velocity'] = {
init: function () {
this.appendDummyInput()
.appendField("set velocity");
this.appendValueInput("X")
.setCheck("Number")
.appendField("forward");
this.appendValueInput("Y")
.setCheck("Number")
.appendField("left");
this.appendValueInput("Z")
.setCheck("Number")
.appendField("up");
this.appendDummyInput()
.appendField("relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false)
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Set the drone velocity in meters per second (cancels navigation requests).");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(considerFrameId);
}
};
Blockly.Blocks['setpoint'] = {
init: function () {
this.appendDummyInput()
.appendField("set");
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown([["velocity", "VELOCITY"], ["attitude", "ATTITUDE"], ["angular rates", "RATES"]]), "TYPE");
this.appendValueInput("VX")
.setCheck("Number")
.appendField("vx");
this.appendValueInput("VY")
.setCheck("Number")
.appendField("vy");
this.appendValueInput("VZ")
.setCheck("Number")
.appendField("vz");
this.appendValueInput("PITCH")
.setCheck("Number")
.appendField("pitch")
.setVisible(false);
this.appendValueInput("ROLL")
.setCheck("Number")
.appendField("roll")
.setVisible(false);
this.appendValueInput("YAW")
.setCheck("Number")
.appendField("yaw")
.setVisible(false);
this.appendValueInput("THRUST")
.setCheck("Number")
.appendField("thrust")
.setVisible(false);
this.appendDummyInput('RELATIVE_TO')
.appendField("relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false);
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Set the drone's setpoints of different types (cancels navigation requests).");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(updateSetpointBlock);
}
};
Blockly.Blocks['rangefinder_distance'] = {
init: function () {
this.appendDummyInput()
.appendField("current rangefinder distance");
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['get_position'] = {
init: function () {
this.appendDummyInput()
.appendField("current")
.appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD")
.appendField("relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false)
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current position or velocity in meters or meters per second.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(considerFrameId);
}
};
Blockly.Blocks['get_yaw'] = {
init: function () {
this.appendDummyInput()
.appendField("current yaw relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false)
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current yaw in degree (not radian).");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(considerFrameId);
}
};
Blockly.Blocks['get_attitude'] = {
init: function () {
this.appendDummyInput()
.appendField("current")
.appendField(new Blockly.FieldDropdown([["pitch", "PITCH"], ["roll", "ROLL"], ["pitch rate", "PITCH_RATE"], ["roll rate", "ROLL_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian).");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['voltage'] = {
init: function () {
this.appendDummyInput()
.appendField("current")
.appendField(new Blockly.FieldDropdown([["total", "VOLTAGE"], ["cell", "CELL_VOLTAGE"]]), "TYPE")
.appendField("voltage");
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current battery voltage in volts.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['armed'] = {
init: function () {
this.appendDummyInput()
.appendField("armed?");
this.setOutput(true, "Boolean");
this.setColour(COLOR_STATE);
this.setTooltip("Returns if the drone armed.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['mode'] = {
init: function () {
this.appendDummyInput()
.appendField("current flight mode");
this.setOutput(true, "String");
this.setColour(COLOR_STATE);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['wait_arrival'] = {
init: function () {
this.appendDummyInput()
.appendField("wait arrival");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Wait until the drone arrives to the navigation target.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['get_time'] = {
init: function () {
this.appendDummyInput()
.appendField("time");
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current timestamp in seconds.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['arrived'] = {
init: function () {
this.appendDummyInput()
.appendField("arrived?");
this.setOutput(true, "Boolean");
this.setColour(COLOR_STATE);
this.setTooltip("Returns if the drone arrived to the navigation target.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['set_led'] = {
init: function () {
this.appendDummyInput()
.appendField("set LED");
this.appendValueInput("INDEX")
.setCheck("Number");
this.appendValueInput("COLOR")
.setCheck("Colour")
.appendField("to color");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_LED);
this.setTooltip("Set an individual LED to specified color.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['set_effect'] = {
init: function () {
this.appendDummyInput()
.appendField("set LED effect")
.appendField(new Blockly.FieldDropdown([["fill", "FILL"], ["blink", "BLINK"], ["fast blink", "BLINK_FAST"], ["fade", "FADE"], ["wipe", "WIPE"], ["flash", "FLASH"], ["rainbow", "RAINBOW"], ["rainbow fill", "RAINBOW_FILL"]]), "EFFECT");
this.appendValueInput("COLOR")
.setCheck("Colour")
.appendField("with color");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_LED);
this.setTooltip("Set desired LED strip effect.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(function(e) {
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
// Hide color field on some effects
var effect = this.getFieldValue('EFFECT');
var hideColor = effect == 'RAINBOW' || effect == 'RAINBOW_FILL';
this.inputList[1].setVisible(!hideColor);
this.render();
});
}
};
Blockly.Blocks['take_off'] = {
init: function () {
this.appendValueInput("ALT")
.setCheck("Number")
.appendField("take off to");
this.appendDummyInput()
.appendField("wait")
.appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Take off on desired altitude in meters");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['land'] = {
init: function () {
this.appendDummyInput()
.appendField("land");
this.appendDummyInput()
.appendField("wait")
.appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['global_position'] = {
init: function () {
this.appendDummyInput()
.appendField("current")
.appendField(new Blockly.FieldDropdown([["latitude", "LATITUDE"], ["longitude", "LONGITUDE"], ["altitude", "ALTITUDE"]]), "NAME");
this.setOutput(true, "Number");
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['on_take_off'] = {
init: function () {
this.appendStatementInput("TAKE_OFF")
.setCheck(null)
.appendField("When took off");
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['on_landing'] = {
init: function () {
this.appendStatementInput("LAND")
.setCheck(null)
.appendField("When landed");
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['on_armed'] = {
init: function () {
this.appendStatementInput("ARMED")
.setCheck(null)
.appendField("when armed");
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.FieldAngle.WRAP = 180;
Blockly.FieldAngle.ROUND = 10;
Blockly.Blocks['angle'] = {
init: function () {
this.appendDummyInput()
.appendField(new Blockly.FieldAngle(90), "ANGLE");
this.setOutput(true, "Number");
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['set_yaw'] = {
init: function () {
this.appendValueInput("YAW")
.setCheck("Number")
.appendField("rotate by");
this.appendDummyInput()
.appendField("relative to")
.appendField(new Blockly.FieldDropdown([["body", "body"], ["markers map", "aruco_map"], ["last navigate target", "navigate_target"]]), "FRAME_ID");
this.appendDummyInput()
.appendField("wait")
.appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("Rotate the drone to the specified angle in degree (not radian).");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['distance'] = {
init: function () {
this.appendDummyInput()
.appendField("distance to point");
this.appendValueInput("X")
.setCheck("Number")
.appendField("x");
this.appendValueInput("Y")
.setCheck("Number")
.appendField("y");
this.appendValueInput("Z")
.setCheck("Number")
.appendField("z");
this.appendDummyInput()
.appendField("relative to")
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"]]), "FRAME_ID");
this.appendValueInput("ID")
.setCheck("Number")
.appendField("with ID")
.setVisible(false);
this.setInputsInline(false);
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns the distance to the given point in meters.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
this.setOnChange(considerFrameId);
}
};
Blockly.Blocks['wait'] = {
init: function () {
this.appendDummyInput()
.appendField("wait");
this.appendValueInput("TIME")
.setCheck("Number");
this.appendDummyInput()
.appendField("seconds");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
var keys = [['up', 'UP'], ['down', 'DOWN'], ['left', 'LEFT'], ['right', 'RIGHT'], ['space', 'SPACE']];
Blockly.Blocks['key_pressed'] = {
init: function () {
this.appendDummyInput()
.appendField("key")
.appendField(new Blockly.FieldDropdown(keys, "NAME"))
.appendField("pressed");
this.appendStatementInput("PRESSED")
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(230);
this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['gpio_read'] = {
init: function () {
this.appendValueInput("PIN")
.setCheck("Number")
.appendField("read GPIO pin");
this.setOutput(true, "Boolean");
this.setColour(COLOR_GPIO);
this.setTooltip("Returns if there is voltage on a GPIO pin.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['gpio_write'] = {
init: function () {
this.appendValueInput("PIN")
.setCheck("Number")
.appendField("set GPIO pin");
this.appendValueInput("LEVEL")
.setCheck("Boolean")
.appendField("to");
this.setInputsInline(true);
this.setColour(COLOR_GPIO);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setTooltip("Set GPIO pin level.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};
Blockly.Blocks['set_servo'] = {
init: function () {
this.appendValueInput("PIN")
.setCheck("Number")
.appendField("set GPIO pin");
this.appendValueInput("PWM")
.setCheck("Number")
.appendField("to PWM");
this.setInputsInline(true);
this.setColour(COLOR_GPIO);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setTooltip("Set PWM on a GPIO pin to control servo. PWM is specified in range of 5002500 ms.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
};