Canada
Asked
Resolved Resolved by DJ Sures!

JSON To Mjbots Quad Robot Series

My robot wants to communicate via JSON commands (it is controlled via JSON from a browser) but I need to use ARC to talk to it. It also sends a lot of data back over JSON to provide current servo status that would need to be fed back into ARC.  Any thoughts if this can be adapted to communicate with the robot? https://github.com/mjbots/quad/blob/main/mech/web_control_assets/index.html


Related Hardware EZ-B v4

ARC Pro

Upgrade to ARC Pro

Unleash your robot's full potential with the cutting-edge features and intuitive programming offered by Synthiam ARC Pro.

PRO
Synthiam
#2  

@Nink - you sent a link to an HTML file. A JSON file looks similar to XML in that it has labelled parameter tags containing values.

i.e.

{
    "glossary": {
        "title": "example glossary",
      "GlossDiv": {
            "title": "S",
         "GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
               "SortAs": "SGML",
               "GlossTerm": "Standard Generalized Markup Language",
               "Acronym": "SGML",
               "Abbrev": "ISO 8879:1986",
               "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                  "GlossSeeAlso": ["GML", "XML"]
                    },
               "GlossSee": "markup"
                }
            }
        }
    }
}

PRO
Synthiam
#3   — Edited

But, you can also parse the JSON in javascript right in arc..

Here's an example parsing JSON from a string...


var resp = '{ "glossary": { "title": "example glossary" } }';

var myObj = JSON.parse(resp);

print (myObj.glossary.title);

And here's an example of getting JSON from an HTTP get request...


var resp = Net.hTTPGet("http://ip.jsontest.com/";); 

var myObj = JSON.parse(resp);

print (myObj.ip);

PRO
Synthiam
#7  

The json that you would need to send is defined as a class struct in the app.js. You could edit the app.js and print the json to the console. But this is essentially the part that is creating the json - it is stored in a "command" variable and built throughout those lines by extracting data from the webpage objects and joystick.


let command = {
      "command" : {
        "mode" : (() => {
          if (this._mode == "off") { return "stopped"; }
          if (this._mode == "stop") { return "zero_velocity" ;}
          if (this._mode == "idle") { return "rest"; }

          if (!movement_commanded && !force_step &&
              (this._mode == "walk" ||
               this._mode == "pronk")) {
            return "rest";
          }
          if (this._mode == "walk") { return "walk"; }
          if (this._mode == "pronk") { return "jump"; }
          return "zero_velocity";
        })(),
      },
    };

    const record_data = getElement("record_data").checked;
    // Unset leaves the current value alone, but we can just send it
    // every time.
    command["command"]["log"] = record_data ? "enable" : "disable";

    command["command"]["v_R"] = v_R;
    command["command"]["w_R"] = w_R;

    if (pose_RB) {
      command["command"]["rest"] = {
        offset_RB : pose_RB,
      };
    }

    if (command["command"]["mode"] == "jump") {
      command["command"]["jump"] = {
        "acceleration" : this.getJumpAcceleration(),
        "repeat" : getElement("jump_repeat").checked,
      };
    } else if (command["command"]["mode"] == "walk") {
      const height_engaged = (
        this._joystick.down(Joystick.BUTTON_SHOULDER_RB) ||
          this._keys['h']);
      const maximize_flight = getElement("maximize_flight").checked;

      command["command"]["walk"] = {
        "step_height" : height_engaged ?
          Number(getElement("height_scale").value) : 1.0,
        "maximize_flight" : maximize_flight,
      };
    }

    const command_string = JSON.stringify(command);
    getElement('current_json_command').innerHTML = command_string;

    if (this._websocket.readyState == WebSocket.OPEN) {
      this._websocket.send(command_string)   <--------------- SENDS THE JSON
    }
  }

PRO
Synthiam
#8  

*Moved into own thread because it is out of scope for the original thread

PRO
Synthiam
#9  

Their json websocket end point is...


 this._websocket = new WebSocket("ws://" + location.host + "/control");

PRO
Synthiam
#10   — Edited

The JSON data is actually displayed in the mjmech control web interface at the bottom...

User-inserted image

For example...

stop is: {"command":{"mode":"zero_velocity","log":"disable","v_R":[0,0,0],"w_R":[0,0,0]}}

idle is:  {"command":{"mode":"rest","log":"disable","v_R":[0,0,0],"w_R":[0,0,0]}}

And when i press W & S for walking forward backward, i assume?  the v_R first parameter value grows the longer i hold W - so maybe that's the speed? It's: {"command":{"mode":"walk","log":"disable","v_R":[0.19850326650786185,0,0],"w_R":[0,0,0],"walk":{"step_height":1,"maximize_flight":false}}}

And Q & E is turning left right? The w_R last parameter value is positive with E and negative with Q: {"command":{"mode":"walk","log":"disable","v_R":[8.30430736384064e-9,0,0],"w_R":[0,0,0.34211217332152727],"walk":{"step_height":1,"maximize_flight":false}}}

And A & D is straffe, which is disabled in the UI but can be activated in the advance menu. It seems to grow the second parameter of v_R {"command":{"mode":"walk","log":"disable","v_R":[-0.0000013168969403094258,0.038964137912889336,0],"w_R":[0,0,9.238252698349165e-20],"walk":{"step_height":1,"maximize_flight":false}}}

PRO
Synthiam
#11   — Edited

So taking those json queries, you can use the Custom Movement Panel and send json commands based on movements. Use the Javascript's Utility.Map() function to map the ARC speed to what the json query parameters need. Read about Utility.Map here: https://synthiam.com/Support/javascript-api/Utility/map

The ARC speed is a value between 0-255 and it appears the json speed is a floating-point between max -3 and +3.

This bit of code is an example of what you'd have in the custom Movement Panel for moving forward with speed control.


var speedLeft = Utility.map(Movement.getSpeedLeft(), 0, 255, 0, 3)
var speedRight = Utility.map(Movement.getSpeedRight(), 0, 255, 0, 3)

var json = '{"command":{"mode":"walk","log":"disable","v_R":[" + speedLeft + ",0,0],"w_R":[0,0,0],"walk":{"step_height":1,"maximize_flight":false}}}';

ControlCommand("WebSocket Client", "send", json);

And this is what you'd have in the custom Movement Panel for moving backward with speed control (notice the - on the return of the Utility.Map() function)


var speedLeft = -Utility.map(Movement.getSpeedLeft(), 0, 255, 0, 3)
var speedRight = -Utility.map(Movement.getSpeedRight(), 0, 255, 0, 3)

var json = '{"command":{"mode":"walk","log":"disable","v_R":[" + speedLeft + ",0,0],"w_R":[0,0,0],"walk":{"step_height":1,"maximize_flight":false}}}';

ControlCommand("WebSocket Client", "send", json);

And something like this for stop i guess....


ControlCommand("WebSocket Client", "send", '{"command":{"mode":"zero_velocity","log":"disable","v_R":[0,0,0],"w_R":[0,0,0]}}');

PRO
Canada
#12  

Thanks for the help DJ,  PTP was giving me a hand this morning but then we had a minor issue with a servo burn out so project is on hold until I get a new motor :-(

User-inserted image

PRO
Canada
#13   — Edited

FYI I managed to get some of the JSON queries out via inspection in browser  as I tried different things.  There is a lot of chatter going on it sends this to me about 10 times a second (deleted 90% of this as I didn't realize it went for about 10 pages)

{ "timestamp" : "2021-Mar-06 08:00:49.321910", "mode" : "stopped", "mode_start" : "2021-Mar-06 07:58:15.324045", "fault" : "", "state" : { "joints" : [ { "id" : 1, "angle_deg" : 82.152000000000001, "velocity_dps" : 2.79, "torque_Nm" : 0, "temperature_C" : 27, "voltage" : 21, "mode" : 0, "fault" : 0, "kp_Nm" : 0, "ki_Nm" : 0, "kd_Nm" : 0, "feedforward_Nm" : 0, "command_Nm" : 0 },

PRO
Synthiam
#15   — Edited

Also, while it could use the suggestion I had of a custom Movement Panel - if PTP is involved then it would make more sense to make a robot skill. You'll have newtonsoft JSON to work with and it's pretty easy from there.

@PTP to create a custom movement panel, check the source code for the recent DJI Tello drone, which is helpful: https://github.com/synthiam/Control-DJI-Tello-Movement-Panel

Assign to the OnMovement event from the movement manager. And that way you simply send the data to the robot and handle the movement.

PRO
Synthiam
#16   — Edited

Have you tried sending any of the commands that I outlined above?

i.e.


ControlCommand("WebSocket Client", "Open", "ws://192.168.0.1");

while (true) {

  ControlCommand("WebSocket Client", "Send", "{\"command\":{\"mode\":\"walk\",\"log\":\"disable\",\"v_R\":[0.3,0,0],\"w_R\":[0,0,0],\"walk\":{\"step_height\":1,\"maximize_flight\":false}}}");

  sleep(250);
}

PRO
Canada
#17  

Robot just stood up  WOOT!   So happy.

PRO
Synthiam
#18   — Edited

Noice!!!!

Once you figure out the movement commands and speed, then you can use the Custom Movement Panel to rock it

Something like this for Forward. I dunno if that's the correct json or not. But the idea is use the Map function to convert the ARC speed (0-255) into a float of (0-1) for that robot.



var s = Utility.map(
  Movement.getSpeed(),
  0,
  255,
  0,
  0.5);
  
ControlCommand("WebSocket Client", "Send", "{\"command\":{\"mode\":\"walk\",\"log\":\"disable\",\"v_R\":[" + s + ",0,0],\"w_R\":[0,0,0],\"walk\":{\"step_height\":1,\"maximize_flight\":false}}}");

PRO
Canada
#19   — Edited

Thanks for all your help on this DJ.  I have it obeying a bunch of commands.  Still a while to go but he can stand laydown and we are learning to walk.  Lots of fun.