Skip to content

Web Architecture

At some part during the project we looked at some possible improvements to the web app. The main two improvements consisted of:

  • Changing the node-blockly and the react-blockly-drawer to the official main blockly library.
  • Looking for a way to ditch the socket server and find some way to directly connect the two endpoints of the web client and the robot.

Changing the blockly library

Node-Blockly is a library that has a few functions useful for Node projects, and react-blockly-drawer is a library that has a few functions useful for React projects. This seems great - until you figure out that they both have one maintainer and haven’t been updated in two years. A much better alternative would be the official blockly library, as it is updated almost weekly. This provides better long time support, and a better way to share code with other developers.

Changing out the Blockly library seemed relatively easy at first; most of the features of the older library have their counterparts. There were quite a few more subtle differences however, and this took a bit of work to figure out. Changing the confusing JSON custom block definitions to a more readable newer function based format was a bit of a challenge. It ended up looking a bit like this:

The code for creating a custom block now, and the code for creating one in the old web app ### New Code:
Blockly.Blocks[blockTypes.turn] = {
    init() {
        //Array with entry [key, value] for each dropdown entry.
        const dropdown = [["right", "turnRight"], ["left", "turnLeft"]]

        this.setColour(180);
        this.appendDummyInput('dirSelection')
            .appendField("Turn")
            .appendField(new Blockly.FieldDropdown(dropdown), 'direction')
            .appendField(new Blockly.FieldNumber(90, 1, 180, "degrees"))
            .appendField("degrees");
        this.setPreviousStatement(true);
        this.setNextStatement(true);
        this.setTooltip("Move the robot forward or backward");
    }
};

Blockly.JavaScript[blockTypes.turn] = (block) => {
    const dropdown_direction = block.getFieldValue('direction');
    const angle_degrees = block.getFieldValue('degrees');
    return '\nturnDirection("' + dropdown_direction + '",' + angle_degrees + ');';
}
### Old code:
const turn = {
    name: 'turn',
    category: 'Movement',
    block: {
        init: function () {
            this.jsonInit({
                "type": "turn",
                "message0": "Direction %1 Degree %2",
                "args0": [
                    {
                        "type": "field_dropdown",
                        "name": "DIRECTION",
                        "options": [
                            [
                                "Right",
                                "turnRight"
                            ],
                            [
                                "Left",
                                "turnLeft"
                            ]
                        ]
                    },
                    {
                        "type": "field_angle",
                        "name": "DEGREES",
                        "angle": 90
                    }
                ],
                "previousStatement": null,
                "nextStatement": null,
                "colour": 180,
                "tooltip": "Make the robot turn",
                "helpUrl": ""
            });
        },
    },
    generator: (block) => {
        const dropdown_direction = block.getFieldValue('DIRECTION');
        const angle_degrees = block.getFieldValue('DEGREES');
        return '\nturnDirection("' + dropdown_direction + '",' + angle_degrees + ');';
    },
};

Besides this the toolbox generation got cleaned up: there is now a seperate file for the base toolbox and whatever custom blocks are defined in their own component are automatically added to the toolbox:

createToolbox()
{
    Blockly.getMainWorkspace()?.dispose();

    let toolbox = `<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox" style="display: none">`;

    toolbox += `<Category name="Servo Control" colour="100">`;
    for (const blockType in blockTypes) {
        toolbox += `<block type="${blockType}" id="${blockType}"></block>`;
    }
    toolbox += `</Category>`;

    toolbox += mainToolboxContent;
    toolbox += `</xml>`;

    Blockly.inject('blockly-toolbox', {
        readOnly: false,
        trashcan: false,
        move: {
            scrollbars: true,
            drag: true,
            wheel: true,
        },
        media: '/assets/',
        toolbox,
    });
}

Socket server possibilities

The socket server is a server that runs online and listens to any WeMos trying to connect. It is a simple server that relays messages. The client similarly searches for the socket server and connects to it. Once both are connected, the client can communicate with the WeMos using the ID, or more like a name, given to each individual WeMos. Using an online server is a bit of a pain, so we decided to look at possibilities on local networks. A big asterisk here is that the host computer should still have internet access.

There were a few configurations we thought about, however the issue always ended up being that the location of either the web client or the WeMos would be unknown to the other, as both endpoints would be assigned a random IP address.

Middleman device

A possible solution that could be looked into more would be a single WeMos (or other network device) that is both connected to the internet and functions as a local hotspot that the WeMos automatically looks for. This would replace the socketserver that exists on a remote server with the hotspot device that has full control over seeing all connected devices. This would allow the WeMos to be able to communicate with the web client, while still maintaining internet access.

Tunneling

One other option that we looked into was a way to create virtual networks using tunneling. This technique seemed to be a possible help due to trying to remove remote networks from the equation - however this wouldn’t solve the problem of neither the WeMos nor the web client knowing the other’s IP address. This option was also not quite fruitful enough to look further into.

Conclusion

Investing time and effort into these possibilities might be too much trouble though - the solutions don’t provide an easier user experience, and they complicate the architecture of the project. The socket server works. It might be a bit overkill for what we are trying to achieve, but ultimately helps in one of the core design pillars of this project: accessibility and ease of use. The fact that the WeMos can just search for a single monolith online means that there are few possible errors, and those that do arise are often either trivial or easy to fix, like a reboot of the socketserver, creating a firewall exception etc.


Last update: June 26, 2022