Skip to content

lupyuen/nuttx-blockly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

(Homage to MakeCode) Coding Ox64 BL808 SBC the Drag-n-Drop Way

(Homage to MakeCode) Coding Ox64 BL808 SBC the Drag-n-Drop Way

Read the article...

MakeCode for BBC micro:bit is an awesome creation that's way ahead of its time (7 years ago!)

MakeCode App Builder

Today 7 years later: How would we redo all this? With a bunch of Open Source Packages?

Blockly App Builder with NuttX

Which will look like this...

Running our Drag-n-Drop App on NuttX Emulator

Create the Blockly Project

Read the article...

MakeCode was created with Blockly, we'll stick with Blockly.

Based on the Blockly Instructions...

npx @blockly/create-package app nuttx-blockly --typescript
npm run build

Try the Blockly Demo: https://lupyuen.github.io/nuttx-blockly/

Send a Command to NuttX Emulator

Read the article...

To send a command to NuttX Emulator: jslinux.js

let send_str = "";
function send_command(cmd) {
  if (cmd !== null) { send_str = cmd; }
  if (send_str.length == 0) { return; }
  console_write1(send_str.charCodeAt(0));
  send_str = send_str.substring(1);
  window.setTimeout(()=>{ send_command(null); }, 10);
}
const cmd = [
  `qjs`,
  `function main() { console.log(123); }`,
  `main()`,
  ``
].join("\r");
window.setTimeout(()=>{ send_command(cmd); }, 10000);

Which will start QuickJS and run a JavaScript Function:

https://lupyuen.github.io/nuttx-tinyemu/blockly/

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs
QuickJS - Type "\h" for help
qjs > function main() { console.log(123); }
undefined
qjs > main()
123
undefined
qjs >

Add POSIX Blocks to Blockly

Read the article...

Based on the Blockly Developer Tools, we add the POSIX Blocks for open(), close(), ioctl() and sleep()...

  1. Add Blocks for POSIX Open and Close

  2. Add POSIX ioctl block

  3. Add POSIX sleep block

  4. Change the Types from String to Number

  5. Clean up parameter names

  6. Create POSIX Category in Toolbox

Then we build and deploy our Blockly Website...

npm run build && rm -r docs && mv dist docs

Let's test it...

Drag-n-Drop a NuttX App for Ox64 BL808

Read the article...

Click this link: https://lupyuen.github.io/nuttx-blockly/

Then Drag-n-Drop this NuttX App...

(Watch the Demo on YouTube)

var ULEDIOC_SETALL, fd, ret;
ULEDIOC_SETALL = 7427;
fd = os.open('/dev/userleds');
for (var count = 0; count < 20; count++) {
  ret = os.ioctl(fd, ULEDIOC_SETALL, 1);
  os.sleep(20000);
  ret = os.ioctl(fd, ULEDIOC_SETALL, 0);
  os.sleep(20000);
}
os.close(fd);

(Homage to MakeCode) Coding Ox64 BL808 SBC the Drag-n-Drop Way

Click the "Run on Ox64 Emulator" button.

Our Drag-n-Drop NuttX App runs automatically in the Emulator yay!

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs
QuickJS - Type "\h" for help
qjs > var ULEDIOC_SETALL, fd, ret;
undefined
qjs >
qjs >
qjs > ULEDIOC_SETALL = 7427;
7427
qjs > fd = os.open('/dev/userleds');
3
qjs > for (var count = 0; count < 20; count++) {
{  ...       ret = os.ioctl(fd, ULEDIOC_SETALL, 1);
{  ...       os.sleep(20000);
{  ...       ret = os.ioctl(fd, ULEDIOC_SETALL, 0);
{  ...       os.sleep(20000);
{  ...     }
bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000

Running our Drag-n-Drop App on NuttX Emulator

How did Blockly pass the Generated JavaScript to NuttX Emulator?

When we click the "Run on Emulator" button, our Blockly Website saves the Generated JavaScript to the Web Browser Local Storage: index.ts

function runEmulator() {
  // Save the Generated JavaScript Code to LocalStorage
  const code = javascriptGenerator.workspaceToCode(ws);
  window.localStorage.setItem("runCode", code);

  // Set the Timestamp for Optimistic Locking (later)
  window.localStorage.setItem("runTimestamp", Date.now() + "");

  // Open the NuttX Emulator. Reuse the same tab.
  window.open("https://lupyuen.github.io/nuttx-tinyemu/blockly/", "Emulator");
}

In the NuttX Emulator: We read the Generated JavaScript from the Web Browser Local Storage. And feed it (character by character) to the NuttX Console: jslinux.js

// QuickJS Command to be sent
const cmd = [
  `qjs`,
  window.localStorage.getItem("runCode"),
  ``
].join("\r");

// Wait for NuttX to boot in 5 seconds. Then send the QuickJS Command.
window.setTimeout(()=>{ send_command(cmd); }, 5000);

// Send a Command to NuttX Console, character by character
let send_str = "";
function send_command(cmd) {
  if (cmd !== null) { send_str = cmd; }
  if (send_str.length == 0) { return; }
  console_write1(send_str.charCodeAt(0));
  send_str = send_str.substring(1);
  window.setTimeout(()=>{ send_command(null); }, 10);
}

Drag-n-Drop a NuttX App to a Real Ox64 BL808 SBC

Read the article...

From NuttX Emulator to a Real NuttX Device! Click this link: https://lupyuen.github.io/nuttx-blockly/

Then Drag-n-Drop the same NuttX App (see the previous section)...

var ULEDIOC_SETALL, fd, ret;
ULEDIOC_SETALL = 7427;
fd = os.open('/dev/userleds');
for (var count = 0; count < 20; count++) {
  ret = os.ioctl(fd, ULEDIOC_SETALL, 1);
  os.sleep(20000);
  ret = os.ioctl(fd, ULEDIOC_SETALL, 0);
  os.sleep(20000);
}
os.close(fd);

(Homage to MakeCode) Coding Ox64 BL808 SBC the Drag-n-Drop Way

  1. Click the "Run on Ox64 Device" button

  2. Click the "Connect" button to connect to our Ox64 BL808 SBC

  3. Power on our Ox64 SBC. The Web App waits for the "nsh>" prompt.

  4. Then our Drag-n-Drop NuttX App runs automatically on a Real Ox64 BL808 SBC yay!

(Watch the Demo on YouTube)

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs
QuickJS - Type "\h" for help
qjs > var ULEDIOC_SETALL, fd, ret;
undefined
qjs >
qjs >
qjs > ULEDIOC_SETALL = 7427;
7427
qjs > fd = os.open('/dev/userleds');
3
qjs > for (var count = 0; count < 20; count++) {
{  ...       ret = os.ioctl(fd, ULEDIOC_SETALL, 1);
{  ...       os.sleep(20000);
{  ...       ret = os.ioctl(fd, ULEDIOC_SETALL, 0);
{  ...       os.sleep(20000);
{  ...     }
bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000

Running our Drag-n-Drop App on Ox64 BL808 SBC

How did Blockly pass the Generated JavaScript to Ox64 SBC?

When we click the "Run on Device" button, our Blockly Website saves the Generated JavaScript to the Web Browser Local Storage: index.ts

// Run on Ox64 Device
function runDevice() {
  // Save the Generated JavaScript Code to LocalStorage
  const code = javascriptGenerator.workspaceToCode(ws);
  window.localStorage.setItem("runCode", code);

  // Set the Timestamp for Optimistic Locking (later)
  window.localStorage.setItem("runTimestamp", Date.now() + "");

  // Open the WebSerial Monitor. Reuse the same tab.
  window.open("https://lupyuen.github.io/nuttx-tinyemu/webserial/", "Device");
}

In the WebSerial Monitor: We read the Generated JavaScript from the Web Browser Local Storage. And feed it (character by character) to the NuttX Console: webserial.js

// Control Ox64 over UART
// https://developer.chrome.com/docs/capabilities/serial
async function control_device() {
    if (!navigator.serial) { const err = "Web Serial API only works with https://... and file://...!"; alert(err); throw new Error(err); }

    // Prompt user to select any serial port.
    const port = await navigator.serial.requestPort();
    term.write("Power on our NuttX Device and we'll wait for \"nsh>\"\r\n");

    // Get all serial ports the user has previously granted the website access to.
    // const ports = await navigator.serial.getPorts();

    // Wait for the serial port to open.
    // TODO: Ox64 only connects at 2 Mbps, change this for other devices
    await port.open({ baudRate: 2000000 });

    // Prepare to write to serial port
    const textEncoder = new TextEncoderStream();
    const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
    const writer = textEncoder.writable.getWriter();
    
    // Read from the serial port
    const textDecoder = new TextDecoderStream();
    const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
    const reader = textDecoder.readable.getReader();

    // Wait for "nsh>"
    let nshSpotted = false;
    let termBuffer = "";

    // Listen to data coming from the serial device.
    while (true) {
        const { value, done } = await reader.read();
        if (done) {
            // Allow the serial port to be closed later.
            reader.releaseLock();
            break;
        }
        // Print to the Terminal
        term.write(value);
        // console.log(value);

        // Wait for "nsh>"
        if (nshSpotted) { continue; }
        termBuffer += value;
        if (termBuffer.indexOf("nsh>") < 0) { continue; }

        // NSH Spotted!
        console.log("NSH Spotted!");
        nshSpotted = true;

        // Send a command to serial port. Newlines become Carriage Returns.
        const code = window.localStorage.getItem("runCode")
            .split('\n').join('\r');
        const cmd = [
            `qjs`,
            code,
            ``
        ].join("\r");
        window.setTimeout(()=>{ send_command(writer, cmd); }, 1000);
    }
}

// Send a Command to serial port, character by character
let send_str = "";
async function send_command(writer, cmd) {
    if (cmd !== null) { send_str = cmd; }
    if (send_str.length == 0) { return; }

    // Get the next character
    const ch = send_str.substring(0, 1);
    send_str = send_str.substring(1);

    // Slow down at the end of each line
    const timeout = (ch === "\r")
        ? 3000
        : 10;

    // Send the character
    await writer.write(ch);
    window.setTimeout(()=>{ send_command(writer, null); }, timeout);
}

More about the Web Serial API...

Connect to Ox64 BL808 SBC via Web Serial API

Read the article...

Let's connect to Ox64 BL808 SBC in our Web Browser via the Web Serial API...

Beware, Web Serial API is only available...

  • Over HTTPS: https://...

  • Or Local Filesystem: file://...

  • It won't work over HTTP! http://...

We create a button: index.html

<button id="connect" onclick="control_device();">
  Connect
</button>

Which connects to Ox64 over UART: jslinux.js

// Control Ox64 over UART
// https://developer.chrome.com/docs/capabilities/serial
async function control_device() {
    if (!navigator.serial) { const err = "Web Serial API only works with https://... and file://...!"; alert(err); throw new Error(err); }

    // Prompt user to select any serial port.
    const port = await navigator.serial.requestPort();

    // Get all serial ports the user has previously granted the website access to.
    // const ports = await navigator.serial.getPorts();

    // Wait for the serial port to open.
    // TODO: Ox64 only connects at 2 Mbps, change this for other devices
    await port.open({ baudRate: 2000000 });

    // Read from the serial port
    const textDecoder = new TextDecoderStream();
    const readableStreamClosed = port.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) {
            // Allow the serial port to be closed later.
            reader.releaseLock();
            break;
        }
        // value is a string.
        console.log(value);
    }

And Ox64 NuttX appears in our JavaScript Console yay!

Starting kernel ...
ABC
bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000
bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000
NuttShell (NSH) NuttX-12.4.0-RC0
nsh>

Send a Command to Ox64 BL808 SBC via Web Serial API

Read the article...

This is how we send a command to Ox64 BL808 SBC via Web Serial API: jslinux.js

  // Wait for the serial port to open.
  // TODO: Ox64 only connects at 2 Mbps, change this for other devices
  await port.open({ baudRate: 2000000 });

  // Send a command to serial port
  const cmd = [
      `qjs`,
      `function main() { console.log(123); }`,
      `main()`,
      ``
  ].join("\r");
  const textEncoder = new TextEncoderStream();
  const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
  const writer = textEncoder.writable.getWriter();
  await writer.write(cmd);
  
  // Read from the serial port
  const textDecoder = new TextDecoderStream();
  const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
  const reader = textDecoder.readable.getReader();

  // Listen to data coming from the serial device.
  ...

And it works! Says the JavaScript Console...

function main() { console.log(123); }
main()
123
undefined
qjs >

Load the Blocks for a Blockly App

Read the article...

This is how we load the Blocks for a Blockly App: index.ts

// Select a Demo
function selectDemo(ev: Event) {
  const storageKey = 'mainWorkspace';
  const target = ev?.target as HTMLSelectElement;
  const value = target.value;

  // Set the Blocks in Local Storage
  switch (value) {
    case "LED Blinky":
      window.localStorage?.setItem(storageKey, '{"blocks":{"languageVersion":0,"blocks":[{"type":"variables_set","id":"Nx6o0xVxp@qzI_(vRd.7","x":60,"y":33,"fields":{"VAR":{"id":":,DB,f}1q3KOBim#j66["}},"inputs":{"VALUE":{"block":{"type":"math_number","id":"enmYd`#z_G1k5Pvv*x(G","fields":{"NUM":7427}}}},"next":{"block":{"type":"variables_set","id":"f#C+(eT=naKZzr%/;A.P","fields":{"VAR":{"id":"A/TX@37C_h*^vbRp@1fz"}},"inputs":{"VALUE":{"block":{"type":"posix_open","id":"^$p+x^F[mQ;grqANDtO}","inputs":{"FILENAME":{"shadow":{"type":"text","id":"nz;|U#KPVW$$c0?W0ROv","fields":{"TEXT":"/dev/userleds"}}}}}}},"next":{"block":{"type":"controls_repeat_ext","id":"0{4pA@{^=ks|iVF.|]i#","inputs":{"TIMES":{"shadow":{"type":"math_number","id":"=o3{$E2c=BpwD0#MR3^x","fields":{"NUM":20}}},"DO":{"block":{"type":"variables_set","id":"l;AmIPhJARU{C)0kNq6`","fields":{"VAR":{"id":"xH3`F~]tadlX:/zKQ!Xx"}},"inputs":{"VALUE":{"block":{"type":"posix_ioctl","id":"0i!pbWJ(~f~)b^@jt!nP","inputs":{"FD":{"block":{"type":"variables_get","id":"QMGa_}UmC$b[5/Bh^f${","fields":{"VAR":{"id":"A/TX@37C_h*^vbRp@1fz"}}}},"REQ":{"block":{"type":"variables_get","id":"dZ5%B_rcbVb_o=v;gze-","fields":{"VAR":{"id":":,DB,f}1q3KOBim#j66["}}}},"ARG":{"block":{"type":"math_number","id":"9UA!sDxmf/=fYfxC6Yqa","fields":{"NUM":1}}}}}}},"next":{"block":{"type":"posix_sleep","id":"ruh/q4F7dW*CQ,5J]E%w","inputs":{"MS":{"block":{"type":"math_number","id":"9~q0@ABEg4VXP:1HN-$1","fields":{"NUM":5000}}}},"next":{"block":{"type":"variables_set","id":"e;BNsjvbN}9vTTc[O#bY","fields":{"VAR":{"id":"xH3`F~]tadlX:/zKQ!Xx"}},"inputs":{"VALUE":{"block":{"type":"posix_ioctl","id":"-G5x~Y4iAyVUAWuwNh#H","inputs":{"FD":{"block":{"type":"variables_get","id":"vtt5Gid0B|iK![$4Ct*D","fields":{"VAR":{"id":"A/TX@37C_h*^vbRp@1fz"}}}},"REQ":{"block":{"type":"variables_get","id":"pd~f}Oqz2(`o3Oz;8ax`","fields":{"VAR":{"id":":,DB,f}1q3KOBim#j66["}}}},"ARG":{"block":{"type":"math_number","id":"OS(uQV)!%iqZ=N}s1H(L","fields":{"NUM":0}}}}}}},"next":{"block":{"type":"posix_sleep","id":"{X9leD=Rgr4=o5E2(#Z,","inputs":{"MS":{"block":{"type":"math_number","id":"eEq(yXcGPbVtZT|CunT0","fields":{"NUM":5000}}}}}}}}}}}}},"next":{"block":{"type":"posix_close","id":"+%kD6{Xa@#BOx}a^Jbup","inputs":{"FD":{"block":{"type":"variables_get","id":"nu)^gdR-9QV71GSI7#(l","fields":{"VAR":{"id":"A/TX@37C_h*^vbRp@1fz"}}}}}}}}}}}}]},"variables":[{"name":"fd","id":"A/TX@37C_h*^vbRp@1fz"},{"name":"ULEDIOC_SETALL","id":":,DB,f}1q3KOBim#j66["},{"name":"ret","id":"xH3`F~]tadlX:/zKQ!Xx"}]}');
      break;
    default:
      break;
  }

  // Refresh the Blocks
  if (ws) { 
    load(ws); 
    runCode();
  }
}

To see the Blocks for a Blockly App: Browse to https://lupyuen.github.io/nuttx-blockly/

Select "Menu > More Tools > Developer Tools > Application > Local Storage > lupyuen.github.io > mainWorkspace"

Or do this from the JavaScript Console...

// Display the Blocks in JSON Format
localStorage.getItem("mainWorkspace");

// Set the Blocks in JSON Format.
// Change `...` to the JSON of the Blocks to be loaded.
localStorage.setItem("mainWorkspace", `...`);