Serial communication for mBlock Extension (Live mode)


#1

Hi everyone, I seem to have an issue trying to communicate through serial with mBlock in LiveMode, I was trying to write an extension to make a step piano mat using an ESP32, I did develop a small library to upload files to an ESP32 that is running Micropython but when I tried to read serial output using device.readRaw() or device.getReactor() I wasn’t able to, looking through the returning Object that this methods returns it looks like a Promise, but trying to await it or to use .then() seem to just result in a timed out promise error, does anybody know how to communicate through serial in LiveMode or if there is any workaround?

Thanks in advance!


#2

@CommandeR @tech_support any ideas?


#3

Ok, after breaking down how mBlock works it looks like the Worker (Sandbox for the extension) where the extensions is running doesn’t have access to DeviceContext (to call readRaw or any read for that matter), on the Worker we have the deviceId and AppContext, the sandbox uses this as context

var __context = {
  app: self.rpc.remote.app,
  getDevice: function (deviceId) {
      return new Proxy(
          {},
          {
              get: function get(target, name) {
                  return "id" == name ? 
                  deviceId : 
                  function () {
                      for (
                          var runDevice = __context.app.runDevice,
                          _len = arguments.length,
                          args = Array(_len),
                          _key = 0;
                          _key < _len;
                          _key++
                      )
                      args[_key] = arguments[_key];
                      return runDevice.apply(void 0, [deviceId, name].concat(args));
                  };
              },
          }
      );
  },
};

But app.runDevice doesn’t exist on the AppContext, here are the methods for app

["constructor","initDrawable","audioPlayer","initAudio","setXY","_getRenderedDirectionAndScale","setDirection","setDraggable","setVisible","setSize","setEffect","clearEffects","setCostume","addCostume","renameCostume","deleteCostume","addSound","renameSound","deleteSound","setRotationStyle","getCostumeIndexByName","getCurrentCostume","getCostumes","reorderCostume","reorderSound","getSounds","updateAllDrawableProperties","getName","isSprite","getBounds","getBoundsForBubble","isTouchingObject","isTouchingPoint","isTouchingEdge","isTouchingSprite","isTouchingColor","colorIsTouchingColor","getLayerOrder","goToFront","goToBack","goForwardLayers","goBackwardLayers","goBehindOther","keepInFence","makeClone","duplicate","onGreenFlag","onStopAll","postSpriteInfo","startDrag","stopDrag","toJSON","dispose"]

so when onRead is called or any event for that matter device will always be an empty object instance ({}), here is the implementation on mBlock side.

  //...
  runExtension: function (srcMethod, deviceId) {
    var method = ExtHandler[srcMethod];
    if (!method) return null;
    var app = __context.app;
    if (deviceId) {
      var device = __context.getDevice(deviceId);
      return method(app, device);
    }
    return method(app);
  },
 //...
 onRead: function onRead(app, device) {
      this.worker.remote.runExtension('onRead', device.id);
 },

#4

I did it! I found a workaround to use the live mode, going to try and make a library out of it, I will put an update here once is done!


#5

@negagen That’s awesome! Be sure to share. :slight_smile:


#6

To read serial from mBlock you can do it by fixing a bug that mBlock has, You can run this code to fix it (paste it on your common code):

if (typeof JSON.decycle !== "function") {
    JSON.decycle = function decycle(object, replacer) {
        "use strict";

        var objects = new WeakMap();     // object to path mappings

        return (function derez(value, path) {


            var old_path;   // The path of an earlier occurance of value
            var nu;         // The new object or array


            if (replacer !== undefined) {
                value = replacer(value);
            }

            if (
                typeof value === "object"
                && value !== null
                && !(value instanceof Boolean)
                && !(value instanceof Date)
                && !(value instanceof Number)
                && !(value instanceof RegExp)
                && !(value instanceof String)
            ) {

                old_path = objects.get(value);
                if (old_path !== undefined) {
                    return { $ref: old_path };
                }


                objects.set(value, path);


                if (Array.isArray(value)) {
                    nu = [];
                    value.forEach(function (element, i) {
                        nu[i] = derez(element, path + "[" + i + "]");
                    });
                } else {


                    nu = {};
                    Object.keys(value).forEach(function (name) {
                        nu[name] = derez(
                            value[name],
                            path + "[" + JSON.stringify(name) + "]"
                        );
                    });
                }
                return nu;
            }
            return value;
        }(object, "$"));
    };
}

if (typeof JSON.originalStringify !== "function") {
    JSON.originalStringify = JSON.stringify
    JSON.stringify = function (obj, replace, space) {
        try {
            return JSON.originalStringify(obj, replace, space)
        } catch (e) {
            if (e.message.includes("Converting circular structure to JSON")) {
                return JSON.originalStringify(JSON.decycle(obj), replace, space)
            }
            throw e
        }
    }
}

then you can read an array-like object using this line on onRead

const port = await app._mCtx.PortManager.getPortByTargetId(device.id)
const reactors = await app._mCtx.deviceCtx._pool._reactors.valueOf()
const buffer = reactors[`${device.id}_${port.address}`].buffer // array-like object

you can use Buffer class to decode the array-like object to text if you want

Buffer.from(buffer).toString('utf8')

#7

@negagen Awesome! Thanks for sharing. :blush: