diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..28c475a51c0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [lupyuen] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['paypal.me/lupyuen'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/README.md b/README.md index a5ecda70108..933e96493a0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,100 @@ -# Blockly for uLisp on RISC-V BL602 +# Blockly for uLisp on RISC-V BL602 (Support for BL602 Simulator in WebAssembly) -See https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp +Browser-based graphical development tool that runs uLisp scripts on BL602 RISC-V Board. + +Read the article... + +- [__uLisp and Blockly on PineCone BL602 RISC-V Board__](https://lupyuen.github.io/articles/lisp) + +Watch the demo on YouTube... + +- [__LED Demo__](https://youtu.be/RRhzW4j8BtI) + +- [__Blinky Demo__](https://youtu.be/LNkmUIv7ZZc) + +- [__BL602 Simulator Demo__](https://youtu.be/Ag2CERd1OzQ) + +Try it here... + +https://appkaki.github.io/blockly-ulisp/demos/code/ + +uLisp Firmware needs to be installed on the BL602 Board... + +https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp + +Or try it with the BL602 Simulator in WebAssembly... + +https://appkaki.github.io/blockly-ulisp/demos/simulator/ + +## Lisp Code Generator + +The following have been added into the existing [`generators`](generators) folder to generate Lisp code and to add blocks specific to uLisp... + +- [`generators/lisp.js`](generators/lisp.js): Main interface for Lisp Code Generator + +- [`generators/lisp`](generators/lisp): Lisp Code Generator for various blocks + +- [`generators/lisp/lisp_library.xml`](generators/lisp/lisp_library.xml): Blocks XML file used by Block Exporter to generate the custom blocks + +The Lisp Code Generator is __incomplete__. The only blocks supported are... + +1. Forever + +1. On Start + +1. Wait + +1. GPIO Digital Write + +The Lisp Code Generator is based on Visual Embedded Rust... + +https://lupyuen.github.io/articles/advanced-topics-for-visual-embedded-rust-programming + +## Demo for Lisp Code Generator + +Watch the demo on YouTube... + +- [__LED Demo__](https://youtu.be/RRhzW4j8BtI) + +- [__Blinky Demo__](https://youtu.be/LNkmUIv7ZZc) + +- [__BL602 Simulator Demo__](https://youtu.be/Ag2CERd1OzQ) + +Try it here... + +https://appkaki.github.io/blockly-ulisp/demos/code/ + +Or try it with the BL602 Simulator in WebAssembly... + +https://appkaki.github.io/blockly-ulisp/demos/simulator/ + +The Blockly demo at [`demos/code`](demos/code) has been customised to include the Lisp Code Generator... + +- [`demos/code/index.html`](demos/code/index.html): Customised to load the Lisp Code Generator and Lisp Blocks + +- [`demos/code/code.js`](demos/code/code.js): Customised to load the Lisp Code Generator and Lisp Blocks + +The Blockly demo calls the [__Web Serial API__](https://web.dev/serial/) to transfer the generated uLisp Script to BL602... + +https://github.com/AppKaki/blockly-ulisp/blob/master/demos/code/code.js#L641-L738 + +We assume that BL602 is running the uLisp Firmware and connected to our computer via USB... + +https://github.com/lupyuen/bl_iot_sdk/tree/ulisp/customer_app/sdk_app_ulisp + +The BL602 Simulator HTML and JavaScript files are here... + +- [`demos/simulator/index.html`](demos/simulator/index.html): Customised to load the Lisp Code Generator, Lisp Blocks and BL602 Simulator + +- [`demos/simulator/code.js`](demos/simulator/code.js): Customised to load the Lisp Code Generator, Lisp Blocks and BL602 Simulator + +The BL602 Simulator includes uLisp compiled for WebAssembly... + +https://github.com/lupyuen/ulisp-bl602/tree/wasm + +Inspired by MakeCode for BBC micro:bit... + +https://makecode.microbit.org/ # Blockly [![Build Status]( https://travis-ci.org/google/blockly.svg?branch=master)](https://travis-ci.org/google/blockly) diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 3c668add120..d8551475079 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -193,6 +193,14 @@ Blockly.WorkspaceSvg = function( this.addChangeListener(Blockly.Procedures.mutatorOpenListener); } + //// TODO + ////console.log('Blockly.Widgets.flyoutCategory', Blockly.Widgets.flyoutCategory); + if (Blockly.Widgets && Blockly.Widgets.flyoutCategory) { + this.registerToolboxCategoryCallback("WIDGET", //// TODO + Blockly.Widgets.flyoutCategory); + } + //// + /** * Object in charge of storing and updating the workspace theme. * @type {!Blockly.ThemeManager} diff --git a/demos/code/code.js b/demos/code/code.js index 0c27653252f..8f980cef6b5 100644 --- a/demos/code/code.js +++ b/demos/code/code.js @@ -234,14 +234,14 @@ Code.LANG = Code.getLang(); * List of tab names. * @private */ -Code.TABS_ = ['blocks', 'javascript', 'php', 'python', 'dart', 'lua', 'xml']; +Code.TABS_ = ['blocks', 'lisp', 'javascript', 'php', 'python', 'dart', 'lua', 'xml']; /** * List of tab names with casing, for display in the UI. * @private */ Code.TABS_DISPLAY_ = [ - 'Blocks', 'JavaScript', 'PHP', 'Python', 'Dart', 'Lua', 'XML', + 'Blocks', 'Lisp', 'JavaScript', 'PHP', 'Python', 'Dart', 'Lua', 'XML', ]; Code.selected = 'blocks'; @@ -324,6 +324,8 @@ Code.renderContent = function() { var xmlText = Blockly.Xml.domToPrettyText(xmlDom); xmlTextarea.value = xmlText; xmlTextarea.focus(); + } else if (content.id == 'content_lisp') { + Code.attemptCodeGeneration(Blockly.Lisp); } else if (content.id == 'content_javascript') { Code.attemptCodeGeneration(Blockly.JavaScript); } else if (content.id == 'content_python') { @@ -449,6 +451,28 @@ Code.init = function() { Blockly.JavaScript.addReservedWords('code,timeouts,checkTimeout'); Code.loadBlocks(''); + + //// TODO: Added code here + // Load the Lisp Custom Blocks. + var blocks = lisp_blocks; // lisp_blocks defined in lisp_blocks.js + // For each Block... + blocks.forEach(block => { + // Register the Block with Blockly. + Blockly.Blocks[block.type] = { + init: function() { + this.jsonInit(block); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + /* + this.setTooltip(function() { + return 'Add a number to variable "%1".'.replace('%1', + thisBlock.getFieldValue('VAR')); + }); + */ + } + }; + }); + //// if ('BlocklyStorage' in window) { // Hook a save function onto unload. @@ -546,11 +570,37 @@ Code.initLanguage = function() { document.getElementById('trashButton').title = MSG['trashTooltip']; }; +//// TODO: Added code for Web Serial API + /** - * Execute the user's code. - * Just a quick and dirty eval. Catch infinite loops. + * Execute the Lisp code on BL602 with Web Serial API */ Code.runJS = function() { + var code = Blockly.Lisp.workspaceToCode(Code.workspace); + console.log(code); + + // Merge the code lines into commands so that all commands start with "(" + var commands = []; + const lines = code.split("\n"); + lines.forEach(line => { + // Skip blank lines + if (line.trim() == "") { return; } + + if (line[0] == "(") { + // If this line starts with "(", add it as a command + commands.push(line); + } else { + // If this line doesn't start with "(", merge with previous line + const lastIndex = commands.length - 1; + commands[lastIndex] += " " + line; + } + }); + + // Run the merged commands + runCommands(commands); + + /* Previously: Execute the user's code. Just a quick and dirty eval. Catch infinite loops. + Blockly.JavaScript.INFINITE_LOOP_TRAP = 'checkTimeout();\n'; var timeouts = 0; var checkTimeout = function() { @@ -564,7 +614,7 @@ Code.runJS = function() { eval(code); } catch (e) { alert(MSG['badCode'].replace('%1', e)); - } + } */ }; /** @@ -587,3 +637,102 @@ document.write('\n'); document.write('\n'); window.addEventListener('load', Code.init); + +/////////////////////////////////////////////////////////////////////////////// +// Web Serial Interface + +// Run a list of commands on BL602 via Web Serial API +async function runCommands(commands) { + console.log(commands); + + // For each merged command... + for (const command of commands) { + // Send an empty command and check that BL602 responds with "#" + await runWebSerialCommand( + "", // Command + "#" // Expected Response + ); + + // Send the actual command but don't wait for response + await runWebSerialCommand( + command, // Command + null // Don't wait for response + ); + + // Test the reboot command + /* + await runWebSerialCommand( + "reboot", // Command + "Init CLI" // Expected Response + ); + */ + + // TODO: Handle no response or invalid response from BL602 + // TODO: Show the BL602 response + } +} + +// Web Serial Port +var serialPort; + +// Run a command on BL602 via Web Serial API and wait for the expectedResponse (if not null) +// Based on https://web.dev/serial/ +async function runWebSerialCommand(command, expectedResponse) { + // Check if Web Serial API is supported + if (!("serial" in navigator)) { alert("Web Serial API is not supported"); return; } + + // Prompt user to select any serial port + if (!serialPort) { serialPort = await navigator.serial.requestPort(); } + if (!serialPort) { return; } + + // Wait for the serial port to open at 2 Mbps + await serialPort.open({ baudRate: 2000000 }); + + // Capture the events for closing the read and write streams + var writableStreamClosed = null; + var readableStreamClosed = null; + + // Send command to BL602 + { + // Open a write stream + console.log("Writing to BL602: " + command + "..."); + const textEncoder = new TextEncoderStream(); + writableStreamClosed = textEncoder.readable.pipeTo(serialPort.writable); + const writer = textEncoder.writable.getWriter(); + + // Write the command + await writer.write(command + "\r"); + + // Close the write stream + writer.close(); + } + + // Read response from BL602 + if (expectedResponse) { + // Open a read stream + console.log("Reading from BL602..."); + const textDecoder = new TextDecoderStream(); + readableStreamClosed = serialPort.readable.pipeTo(textDecoder.writable); + const reader = textDecoder.readable.getReader(); + + // Listen to data coming from the serial device + while (true) { + const { value, done } = await reader.read(); + if (!done) { console.log(value); } + + // If the stream has ended, or the data contains expected response, we stop + if (done || value.indexOf(expectedResponse) >= 0) { break; } + } + + // Close the read stream + reader.cancel(); + } + + // Wait for read and write streams to be closed + if (readableStreamClosed) { await readableStreamClosed.catch(() => { /* Ignore the error */ }); } + if (writableStreamClosed) { await writableStreamClosed; } + + // Close the port + await serialPort.close(); + console.log("runWebSerial: OK"); +} diff --git a/demos/code/index.html b/demos/code/index.html index 9fd858aeffa..ca4147b9d69 100644 --- a/demos/code/index.html +++ b/demos/code/index.html @@ -4,16 +4,50 @@ - Blockly Demo: + uLisp Blockly + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36,6 +70,8 @@

Blockly‏ > ...   + Lisp +   JavaScript   Python @@ -72,6 +108,7 @@

Blockly‏ >
+

   

   

   

@@ -80,16 +117,43 @@ 

Blockly‏ >