From 7004a9e68e06d27d3bef1123ae5375a77b0546c5 Mon Sep 17 00:00:00 2001 From: cfry Date: Wed, 7 Apr 2021 19:49:20 -0400 Subject: [PATCH] release --- core/dextersim.js | 3 +- core/index.js | 4 +- core/job.js | 16 +-- core/main_eval.py | 5 +- core/py.js | 1 + core/robot.js | 7 +- core/socket.js | 54 ++++++---- core/utils.js | 12 ++- doc/guide.html | 6 +- doc/known_issues.html | 2 - doc/ref_man.html | 147 ++++++++++++++++++++++++--- doc/release_notes.html | 23 +++++ index.html | 2 +- package.json | 4 +- ready.js | 15 ++- robot_status_dialog.js | 87 +++++++++------- simulator/simutils.js | 2 +- splash_screen.js | 2 +- user_tools/dexter_user_interface2.js | 2 +- 19 files changed, 291 insertions(+), 103 deletions(-) diff --git a/core/dextersim.js b/core/dextersim.js index 07a41afc..1a72e13b 100644 --- a/core/dextersim.js +++ b/core/dextersim.js @@ -461,7 +461,8 @@ DexterSim = class DexterSim{ } return 0 //dur } - + //The corresponding fn for Dexter causes DexRun to crash, so we've commented out all calls to it. + //When FPGA is updated to support it, we can re_instate it as a regular function. static empty_instruction_queue_now(robot_name){ if(DexterSim.robot_name_to_dextersim_instance_map) { let sim_inst = DexterSim.robot_name_to_dextersim_instance_map[robot_name] diff --git a/core/index.js b/core/index.js index b81e1020..d653a021 100644 --- a/core/index.js +++ b/core/index.js @@ -1,5 +1,5 @@ -global.dde_version = require("../package.json").version -global.dde_release_date = require("../package.json").release_date +global.dde_version = "3.7.9" //require("../package.json").version +global.dde_release_date = "Apr 7, 2021" //require("../package.json").release_date console.log("dde_version: " + global.dde_version + " dde_release_date: " + global.dde_release_date + "\nRead electron_dde/core/job_engine_doc.txt for how to use the Job Engine.\n") diff --git a/core/job.js b/core/job.js index 6a72eeca..3409b91c 100644 --- a/core/job.js +++ b/core/job.js @@ -106,7 +106,7 @@ class Job{ } if (Job[name] && Job[name].is_active()) { //we're redefining the job so we want to make sure the //previous version is stopped. - if (Job[name].robot instanceof Dexter) {Job[name].robot.empty_instruction_queue_now() } + //if (Job[name].robot instanceof Dexter) {Job[name].robot.empty_instruction_queue_now() } Job[name].stop_for_reason("interrupted", "User is redefining this job.") let orig_args = arguments[0] setTimeout(function(){ new Job (orig_args) }, 200) @@ -728,6 +728,7 @@ class Job{ //not focused, pressing the space or ENTER key doesn't do something strange //like an extra button click. const job_instance = Job[job_name] + console.log("Job button clicked when status was: " + job_instance.status_code) if (job_instance.status_code == "suspended"){ if(but_elt.title.includes("Make Instruction")) { job_instance.stop_for_reason("interrupted", "User stopped job.") } else { job_instance.unsuspend() } @@ -738,7 +739,7 @@ class Job{ job_instance.color_job_button() //keep this call } else if(job_instance.is_active()){ - if (job_instance.robot instanceof Dexter) { job_instance.robot.empty_instruction_queue_now() } + //if (job_instance.robot instanceof Dexter) { job_instance.robot.empty_instruction_queue_now() } job_instance.stop_for_reason("interrupted", "User stopped job", false) } else { //restart this job on Dexter @@ -747,7 +748,9 @@ class Job{ } } else if(job_instance.is_active()){ - if (job_instance.robot instanceof Dexter) { job_instance.robot.empty_instruction_queue_now() } + if (job_instance.robot instanceof Dexter) { + //job_instance.robot.empty_instruction_queue_now() //causes DexRun to error. + } job_instance.stop_for_reason("interrupted", "User stopped job", false) } else { @@ -792,7 +795,7 @@ class Job{ job_instance.unsuspend() } else if(job_instance.is_active()){ - if (job_instance.robot instanceof Dexter) { job_instance.robot.empty_instruction_queue_now() } + //if (job_instance.robot instanceof Dexter) { job_instance.robot.empty_instruction_queue_now() } job_instance.stop_for_reason("interrupted", "User stopped job", false) } else { @@ -1258,8 +1261,8 @@ Job.last_job = null Job.stop_all_jobs = function(){ var stopped_job_names = [] for(var j of Job.all_jobs()){ - if (j.robot instanceof Dexter) { j.robot.empty_instruction_queue_now() } - if ((j.stop_reason == null) && (j.status_code != "not_started")){ + //if (j.robot instanceof Dexter) { j.robot.empty_instruction_queue_now() } + if ((j.stop_reason == null) && (j.status_code !== "not_started")){ j.stop_for_reason("interrupted_by_stop_button", "User stopped all jobs.", false) stopped_job_names.push(j.name) } @@ -2052,7 +2055,6 @@ Job.prototype.do_next_item = function(){ //user calls this when they want the jo } } } - //this.color_job_button() //todo needs to work for Dexter.sleep (z) instruction and to undo its yellow. ///also called by Make Instruction for creating string to save. Job.prototype.transform_data_array = function(data_array){ diff --git a/core/main_eval.py b/core/main_eval.py index b1cf5cc2..4a26e5d7 100644 --- a/core/main_eval.py +++ b/core/main_eval.py @@ -2,6 +2,7 @@ import traceback import json +dde_py_globals = {'sys': sys} def main(): print("Python eval process started.\n", flush=True) while True: @@ -24,7 +25,7 @@ def main(): is_py_evalable = True if is_py_evalable: try: - result = eval(src) + result = eval(src, dde_py_globals) if str(type(result)) == "": result = result.tolist() except Exception as err : @@ -35,7 +36,7 @@ def main(): is_error = False else: try: - exec(src) + exec(src, dde_py_globals) except Exception as err: is_error = True result = traceback.format_exc() diff --git a/core/py.js b/core/py.js index 3b5195fd..4ee9087f 100644 --- a/core/py.js +++ b/core/py.js @@ -152,6 +152,7 @@ class Py{ }); this.callbacks = [this.default_callback] //clear out old callbacks, and add back the default. this.eval('sys.path.append("' + dde_apps_folder + '")') + this.eval("dde_apps_folder = '" + dde_apps_folder + "'") } //callback takes 1 arg, a json_object with is_error, source, result properties diff --git a/core/robot.js b/core/robot.js index f47424ba..c52265bf 100644 --- a/core/robot.js +++ b/core/robot.js @@ -1272,6 +1272,9 @@ Dexter = class Dexter extends Robot { } close_robot(){ + out("top of Dexter.close_robot") + //setTimeout(function(){ + //out("top of timeout fn Dexter.close_robot") clearTimeout(this.heartbeat_timeout_obj) //looks like not working this.waiting_for_heartbeat = false this.heartbeat_timeout_obj = null @@ -1280,11 +1283,13 @@ Dexter = class Dexter extends Robot { // delete Dexter[this.name] //don't do this. If the robot is still part of a Job, //and that job is inactive, then we can still "restart" the job, //and as such we want that binding of Robot.this_name to still be around. + //}, 5000) } + /* causes DexRun to crash. re-inswtate when FPGA code rewriten to support this empty_instruction_queue_now(){ Socket.empty_instruction_queue_now(this.name) - } + }*/ //ins_array can be an oplet array or a raw string send(oplet_array_or_string){ diff --git a/core/socket.js b/core/socket.js index e2de8cbf..4139cad1 100755 --- a/core/socket.js +++ b/core/socket.js @@ -16,7 +16,7 @@ var Socket = class Socket{ out("socket for Robot." + robot_name + ". is_connected? " + Robot[robot_name].is_connected) } else if ((sim_actual === false) || (sim_actual == "both")) { - if(!Socket.robot_name_to_ws_instance_map[robot_name]){ + if(!Socket.robot_name_to_soc_instance_map[robot_name]){ try { let ws_inst = new net.Socket() ws_inst.on("data", function(data) { @@ -32,7 +32,7 @@ var Socket = class Socket{ let st_inst = setTimeout(function(){ if(ws_inst.connecting) { //still trying to connect after 1 sec, so presume it never will. kill it out("Socket timeout while connecting to Dexter." + robot_name) - delete Socket.robot_name_to_ws_instance_map[robot_name] + delete Socket.robot_name_to_soc_instance_map[robot_name] ws_inst.destroy() //todo destroy if(connect_error_cb) { connect_error_cb() //stop the job that started this. @@ -42,7 +42,7 @@ var Socket = class Socket{ ws_inst.on("error", function(err){ out("Socket error while connecting to Dexter." + robot_name) clearTimeout(st_inst) - delete Socket.robot_name_to_ws_instance_map[robot_name] + delete Socket.robot_name_to_soc_instance_map[robot_name] ws_inst.destroy() if(connect_error_cb) { connect_error_cb() @@ -51,7 +51,7 @@ var Socket = class Socket{ out("Now attempting to connect to Dexter." + robot_name + " at ip_address: " + rob.ip_address + " port: " + rob.port + " ...", "brown") ws_inst.connect(rob.port, rob.ip_address, function(){ clearTimeout(st_inst) - Socket.robot_name_to_ws_instance_map[robot_name] = ws_inst + Socket.robot_name_to_soc_instance_map[robot_name] = ws_inst Socket.new_socket_callback(robot_name, connect_success_cb) }) } @@ -374,6 +374,10 @@ var Socket = class Socket{ static send(robot_name, oplet_array_or_string){ //can't name a class method and instance method the same thing //onsole.log("Socket.send passed oplet_array_or_string: " + oplet_array_or_string) let rob = Robot[robot_name] + if(rob.waiting_for_instruction_ack) { + dde_error("In Socket.send, attempt to send instruction: " + oplet_array_or_string + + " but still waiting for previous instruction: " + rob.waiting_for_instruction_ack) + } if(oplet_array_or_string !== Socket.resend_instruction){ //we don't want to convert an array more than once as that would have degreees * 3600 * 3600 ... //so only to the convert on the first attempt. oplet_array_or_string = Socket.instruction_array_degrees_to_arcseconds_maybe(oplet_array_or_string, rob) @@ -387,13 +391,17 @@ var Socket = class Socket{ const arr_buff = Socket.string_to_array_buffer(str) const sim_actual = Robot.get_simulate_actual(rob.simulate) if((sim_actual === true) || (sim_actual === "both")){ + rob.waiting_for_instruction_ack = oplet_array_or_string DexterSim.send(robot_name, arr_buff) } if ((sim_actual === false) || (sim_actual === "both")) { - let ws_inst = Socket.robot_name_to_ws_instance_map[robot_name] + let ws_inst = Socket.robot_name_to_soc_instance_map[robot_name] if(ws_inst) { try { + rob.waiting_for_instruction_ack = oplet_array_or_string + //console.log("Socket.send about to send: " + str) ws_inst.write(arr_buff) //if doesn't error, success and we're done with send + //console.log("Socket.send just sent: " + str) Socket.resend_instruction = null Socket.resend_count = null //this.stop_job_if_socket_dead(job_id, robot_name) @@ -458,7 +466,13 @@ var Socket = class Socket{ // static on_receive(data, robot_name, payload_string_maybe){ //data.length == 240 data is of type: Uint8Array, all values between 0 and 255 inclusive - //onsole.log("Socket.on_receive passed data: " + data) + //console.log("Socket.on_receive passed data: " + data) + let rob = Dexter[robot_name] + if(rob.waiting_for_instruction_ack){ } //ok todo check that data has in it the instruction ack that we are expecting. + else { + dde_error("Socket.on_receive is not expecting data from Dexter.") + } + rob.waiting_for_instruction_ack = false let robot_status let oplet if(Array.isArray(data)) { //todo return from sim same data type as Dexter returns. //a status array passed in from the simulator @@ -475,6 +489,7 @@ var Socket = class Socket{ let opcode = robot_status[Dexter.INSTRUCTION_TYPE] oplet = String.fromCharCode(opcode) } + //console.log("Socket.on_receive passed robot status: " + robot_status) //the simulator automatically does this so we have to do it here in non-simulation //out("on_receive got back oplet of " + oplet) robot_status[Dexter.INSTRUCTION_TYPE] = oplet @@ -500,7 +515,7 @@ var Socket = class Socket{ else { Socket.convert_robot_status_to_degrees(robot_status) } - let rob = Dexter[robot_name] + //Socket.robot_is_waiting_for_reply[robot_name] = false rob.robot_done_with_instruction(robot_status) //robot_status ERROR_CODE *might* be 1 } @@ -624,16 +639,21 @@ var Socket = class Socket{ } if ((sim_actual === false) || (sim_actual == "both")){ if((rob.active_jobs_using_this_robot().length == 0) || force_close){ - const ws_inst = Socket.robot_name_to_ws_instance_map[robot_name] - if(ws_inst){ - ws_inst.removeAllListeners() - ws_inst.destroy() - delete Socket.robot_name_to_ws_instance_map[robot_name] + const soc_instance = Socket.robot_name_to_soc_instance_map[robot_name] + if(soc_instance){ + //out("about to destroy socket") + //setTimeout(function(){ + soc_instance.removeAllListeners() + soc_instance.destroy() + delete Socket.robot_name_to_soc_instance_map[robot_name] + //out("destroyed socket") + //}, 5000) } } } } + /*this causes DexRun to crash. Ultimately we need to rewrite FPGA code to get this functionality. static empty_instruction_queue_now(robot_name){ let rob = Robot[robot_name] const sim_actual = Robot.get_simulate_actual(rob.simulate) @@ -641,11 +661,11 @@ var Socket = class Socket{ DexterSim.empty_instruction_queue_now(robot_name) } if ((sim_actual === false) || (sim_actual == "both")){ - const ws_inst = Socket.robot_name_to_ws_instance_map[robot_name] - if(ws_inst && !ws_inst.destroyed){ + const soc_inst = Socket.robot_name_to_soc_instance_map[robot_name] + if(soc_inst && !soc_inst.destroyed){ const oplet_array = make_ins("E") //don't expect to hear anything back from this. const arr_buff = this.oplet_array_or_string_to_array_buffer(oplet_array) - try { ws_inst.write(arr_buff) } //band-aid for not knowing what's in Dexter's queue. + try { soc_inst.write(arr_buff) } //band-aid for not knowing what's in Dexter's queue. //if the queue is empty we shouldn't do. //we should empty the queue whenever DDE detects an error, //but before closing the socket. @@ -655,7 +675,7 @@ var Socket = class Socket{ } } } - } + }*/ } //Socket.robot_is_waiting_for_reply = {} //robot_name to boolean map. @@ -668,7 +688,7 @@ Socket.PAYLOAD_LENGTH = 6 //6th integer array index Socket.resend_instruction = null Socket.resend_count = null -Socket.robot_name_to_ws_instance_map = {} +Socket.robot_name_to_soc_instance_map = {} Socket.DEGREES_PER_DYNAMIXEL_320_UNIT = 0.29 //range of motion sent is 0 to 1023 Socket.DEGREES_PER_DYNAMIXEL_430_UNIT = 360 / 4096 Socket.J6_OFFSET_SERVO_UNITS = 512 diff --git a/core/utils.js b/core/utils.js index 58098364..3d946a56 100644 --- a/core/utils.js +++ b/core/utils.js @@ -442,12 +442,14 @@ module.exports.pad_integer = pad_integer //used in computing numbers to display in the robot_status dialog function to_fixed_smart(num, digits=0){ - if((num == "no status") || (num === undefined)) { return num } - try{ return num.toFixed(digits)} - catch(err){ - warning("to_fixed_smart called with non_number: " + num) - return "" + num + if(typeof(num) === "number") { + try{ return num.toFixed(digits)} + catch(err){ + warning("to_fixed_smart called with non_number: " + num) + return "" + num + } } + else { return num } //presume its a string like "N/A" and leave it alone. } module.exports.to_fixed_smart = to_fixed_smart diff --git a/doc/guide.html b/doc/guide.html index 31337c82..2a462c5b 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -8,8 +8,8 @@
About This is Dexter Development Environment
- version: 3.7.8
- released: Apr 2, 2021 + version: 3.7.9
+ released: Apr 7, 2021

DDE helps you create, debug, and send software to a Dexter robot. You can use any JavaScript augmented with DDE-specific functions to help find out about, @@ -1311,7 +1311,7 @@
Step 3
-
JavaScript +
JavaScript DDE let's you control a Dexter robot with Javascript. You can execute all of JS. DDE extends JavaScript with libraries of functions that control Dexter, allow access to Unix commands on Dexter's computer, allows access to HTML and the DOM, has a convenient window system diff --git a/doc/known_issues.html b/doc/known_issues.html index 3c54d99c..5bfe8454 100644 --- a/doc/known_issues.html +++ b/doc/known_issues.html @@ -10,8 +10,6 @@ with this DDE release. All of them are on our "do list".

    -
  • Py.eval does not work for Python code containing - both expressions and statements
  • The simulator both setting does not work.
  • The simulator does not work for ANGLE, DELTA, PID_DELTA, A2D_SIN, A2D_COS.
  • The Messaging software is not yet intended for public use. We're still testing.
  • diff --git a/doc/ref_man.html b/doc/ref_man.html index 56e37ac7..4f29fb0b 100644 --- a/doc/ref_man.html +++ b/doc/ref_man.html @@ -10097,6 +10097,37 @@

    Cover DDE Window

+
Math in JavaScript +Arithmetic is built-in to JavaScript at top level.
+See DDE menu bar/Learn JS/Math for examples to insert. +

+The class Number has a few constants and utility functions.
+Eval Number to see them. +

+The class Math has quite a few more contants and functions +including trigonometry and exponents.
+Eval Math to see them. +

+"mathjs" is an npm package containing a large number of functions. +It is built-in to DDE. It supports +numbers, big numbers, complex numbers, fractions, units, strings, +arrays, matrices, eigenvalues and symbolic math.
+Overview +Function Reference +

+Examples:
+Initialize "math" to the class containing the mathjs functions
+var math = require("mathjs") +

+Eval math to inspect the list of constants and functions available
+math.hypot(3, 4) => 5 +

+There's a lot more math libraries in npm. Choose File menu/Manage npm. +Type in "math" or the name of some math function like "svd", and +click the button. This will show you a page +of relevant npm packages. +
+
Messaging
Warning: The Messaging facility is now just internal prototype code, not available for public use. @@ -11182,9 +11213,34 @@

Cover DDE Window

Python - DDE is a JavaScript-based development environment. When you have code in the - editor, it is expected to be JavaScript unless -
  • The code is in a file with a ".py" extension.
  • + A language is a collection of tools that are designed to work synergistically + with each other. When possible, its good to keep the code for a program in + one language for this reason. +

    + JavaScript was chosen for DDE because: +
      +
    • It has the largest repository of open source code for any programming language ever.
    • +
    • Its the primary general purpose programming language on the Web.
    • +
    • It has decent support for "eval" (which Python doesn't), necessary for + the kind of interactive IDE that DDE is.
    • +
    + JavaScript, like all programming languages, + especially those popular now, is far from perfect. DDE helps programmers + work around those imperfections. +

    + None-the-less, sometimes its worth it to put up with the difficulties of + "foreign function calls" to get the functionality in other languages. + This can happen because a given functionality isn't available in the primary + language of the app, and its too big to write. Or it may be that the programmer + is more familiar with a library in another language. There are "cultural" + reasons as well, or simply matters of personal taste. +

    + DDE is a JavaScript-based development environment. When you have code in DDE, + it is expected to be JavaScript. But to accomodate useful functionality + in Python, DDE has several ways that DDE programs can take advantage of + existing Python code, or even write new bits of "glue code" between + existing Python and DDE/JavaScript. +
    • The Python code is in a file with a ".py" extension.
    • The code is wrapped in a call to DDE's (JavaScript) function: Py.eval
    • The code is in DDE's command line, and you've selected "Python" @@ -11334,18 +11390,51 @@

      Cover DDE Window

      you kill it, init a new Python process, or quit DDE.

      Examples:

      - Py.eval("len([4, 5])")

      - - Py.eval('foo = "hi from py"')
      - Py.eval('foo')

      - - Py.eval('import math')
      - Py.eval('math.pi')
      + Get the length of a list:
      + Py.eval("len([4, 5])") +

      + Set the variable "foo".
      + Py.eval('foo = "hi from Py"')
      + Get the value of the varible foo:
      + Py.eval('foo') +

      + See the installed modules
      + Note: The "sys" module is automatically installed in + DDE's Python interface process.
      + Py.eval("list(sys.modules.keys())") +

      + See the folders where Python looks for modules to import.
      + Py.eval("sys.path")
      + Add to that array:
      + Py.eval("sys.path.append('/Users/foo/Documents')") +

      + Import the "math" module.
      + Py.eval('import math') +

      + Get the value of a constant in the Math package.
      + Py.eval('math.pi') +

      + Add two numbers and use the result in the callback to Py.eval.
      Py.eval("2 + 3",
               function(json_obj){
                 out(json_obj.source + " => " + json_obj.result)
              })    
      -
      
      +       Redefine foo to 12
      + Py.eval('foo = 12')
      + Get the new value.
      + Py.eval('foo') +

      + Define a function that accesses the global variable "foo" and sets it. +
      Py.eval(`def a_fn():
      +  global foo
      +  foo = foo + 1
      +  return foo`)    
      + Call our newly defined function. Each time we call it, foo is incremented
      + Py.eval('a_fn()') +

      + Define a Job that imports "math" and sets the user_data variable of "j2" to + the result of math.pi * 10 then uses it to move Joint 2. +
      
       new Job({
         name: "my_job",
         user_data: {j2: null},
      @@ -11367,8 +11456,8 @@ 

      Cover DDE Window

      } ] })
      - Like the other examples, the last example isn't practical, but understanding it will help you - integrate Python dynamically into a running job. + Like the other examples, the last example isn't so practical, but understanding it will help you + integrate Python dynamically into a running job. The details:
      1. First we initialize user_data.j2 to null. It's going to hold the joint angle that Python computes.
      2. @@ -11423,6 +11512,30 @@

        Cover DDE Window

        For a simpler way to call Python from within a Job, see Brain.eval_python
+ +
dde_apps_folder for Python + In DDE JavaScript, dde_apps_folder is a global variable bound + to the full path of the dde_apps folder. It's exact value will be different + for each computer, but will be something like + "/Users/Fry/Documents/dde_apps" +

+ This is so convenient that we've made this global variable available in + DDE's Python process as well. + Here's a case where you might use it. We're storing a filename to + write to in a global variable, then using it in a function + that writes "Test47" into a file in your dde_apps folder.
+ Example: +
Py.eval(`filename = dde_apps_folder + "/filename.txt"
+def main():
+  with open(filename, 'w') as f:
+    f.write("Test47")`)   
+ Py.eval("main()") //creates the file and sets its content to "Test47"
+ This is one strategy for getting a large string from Python into DDE JavaScript + via:
+ read_file(dde_apps_folder + "/filename.txt") => "Test47"
+ Note that our designation for the file is the same for both Python and JavaScript. +
+
Py.load_file Imports a file into DDE's Python process.
Parameters:
@@ -11454,7 +11567,9 @@

Cover DDE Window

Ends the Python process. The Python process shouldn't take up much resources when idle. One reason to kill it is to get rid of the state currently in the Python process so the next time Py.eval is called, - it will start afresh. + it will start afresh.
+ Example:
+ Py.kill()
Py.init This function @@ -11469,7 +11584,9 @@

Cover DDE Window

after launching DDE or killing the Python process, so there isn't much need to call this function explicitly. After calling this function, your first call to Py.eval will - be a fraction of a second faster. + be a fraction of a second faster.
+ Example:
+ Py.init()
Troubleshooting Python, like all modern popular programming languages, has diff --git a/doc/release_notes.html b/doc/release_notes.html index 56f1e54f..fa18be46 100644 --- a/doc/release_notes.html +++ b/doc/release_notes.html @@ -6,6 +6,29 @@ .doc_details summary { font-weight: 600; } +
v 3.7.9, Apr 7, 2021 +Highlights: Fixed crashing DexRun due to faulty sending of instruction. + Fixed Py.eval for global variables. + Ref Man new section: "Math in JavaScript" +
    +
  • Fixed DexRun crashing by removing faulty sending of instruction "E".
  • +
  • Welcome to DDE dialog increased height slightly to avoid scroll bar.
  • +
  • Robot Status dialog can now be brought up when no Job has run on the given robot.
  • +
  • Robot Status dialog no longer shows incorrect units for 'Measured Torque".
  • +
  • When you choose "JS" in the command line language menu, + the doc pane scrolls to the User Guide section on "JavaScript".
  • +
  • When the command line has language "Python" selected and you click one some text in it, + now instead of erroring, you get a link to Python documentation in the output pane.
  • +
  • Fixed Py.eval bug for global variables.
  • +
  • Ref Man Py.eval has been extended to be a mini tutorial in Python basics.
  • +
  • The Ref Man intro to Python in DDE has been extended to encompass a wider programming picture.
  • +
  • Python documntation: new examples for Py.init() and Py.kill() added.
  • +
  • Jobs bar height increased to show the bottom of the "circle i" character for inspecting + the Job.
  • +
  • Ref Man new top level section: Math in JavaScript
  • +
+
+
v 3.7.8, Apr 2, 2021 Highlights: Simulator now more accurately reflects Dexter hardware. Simulator new "Show Q" button. diff --git a/index.html b/index.html index 6879600c..5d2d93ac 100644 --- a/index.html +++ b/index.html @@ -796,7 +796,7 @@
Jobs
-
+
diff --git a/package.json b/package.json index b7d43e58..96c23312 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "dexter_dev_env", "productName": "dexter_dev_env", - "version": "3.7.8", - "release_date": "Apr 2, 2021", + "version": "3.7.9", + "release_date": "Apr 7, 2021", "description": "Dexter Development Environment for programming the Dexter robot.", "author": "Fry", "license": "GPL-3.0", diff --git a/ready.js b/ready.js index 9920261e..94a62206 100644 --- a/ready.js +++ b/ready.js @@ -265,7 +265,10 @@ if(full_src.length > 0){ let pos = event.target.selectionStart if(pos < (full_src.length - 1)){ - if(cmd_lang_id.value == "SSH"){ + if(cmd_lang_id.value == "JS"){ + onclick_for_click_help(event) + } + else if(cmd_lang_id.value == "SSH"){ let space_pos = full_src.indexOf(" ") if((space_pos == -1) || (pos < space_pos)){ onclick_for_click_help(event) @@ -273,8 +276,13 @@ //else don't do click help because clicking on the args of a bash cmd //doesn't yield meaningful man help } - else { //JS - onclick_for_click_help(event) + else if (cmd_lang_id.value == "Python"){ + out(`Python doc`, + undefined, true) + } + else { + shouldnt("cmd_input_id got menu item: " + cmd_lang_id.value + + " that has no help.") } } //else don't give help if clicking at very end. @@ -293,6 +301,7 @@ SSH.close_connection() //if no connection. that's ok cmd_menu_id.style.display = "none" cmd_input_id.placeholder = "Type in JS & hit the Enter key to eval" + open_doc(JavaScript_guide_id) } else if(cmd_lang_id.value === "Python"){ open_doc(python_doc_id) diff --git a/robot_status_dialog.js b/robot_status_dialog.js index 8792482c..0ba2dfd7 100644 --- a/robot_status_dialog.js +++ b/robot_status_dialog.js @@ -14,11 +14,6 @@ var RobotStatusDialog = class RobotStatusDialog{ if(!(robot instanceof Dexter)) { robot = (Job.last_job? Job.last_job.robot : Dexter.dexter0) } - if(!robot.robot_status) { - warning("Dexter." + robot.name + " has not had a Job run on it since launching DDE,
" + - "so it has no robot_status yet.") - return - } let content = RobotStatusDialog.make_html_table(robot) let cal = robot.is_calibrated() cal = ((cal === null) ? "unknown" : cal) @@ -124,7 +119,7 @@ var RobotStatusDialog = class RobotStatusDialog{ for (let i = 0; i < 60; i++){ //don't use robot_status.length as there might not BE a robot_status if it hasn't been run yet let label = labels[i] if ((label != null) && !label.startsWith("UNUSED")){ - let val = (robot_status ? robot_status[i] : "no status") //its possible that a robot will have been defined, but never actually run when this fn is called. + let val = (robot_status ? robot_status[i] : "N/A") //its possible that a robot will have been defined, but never actually run when this fn is called. if((typeof(val) == "number") && (i >= 10)) { //display as a real float val = to_fixed_smart(val, 3) //val.toFixed(3) } @@ -141,10 +136,12 @@ var RobotStatusDialog = class RobotStatusDialog{ STOP_TIME_id.title = date_integer_to_long_string(robot_status[Dexter.STOP_TIME]) INSTRUCTION_TYPE_id.title = Robot.instruction_type_to_function_name(robot_status[Dexter.INSTRUCTION_TYPE]) if(window["MEASURED_X_id"]) { - let xyz = robot.rs.xyz()[0] - MEASURED_X_id.innerHTML = this.format_measured_angle(xyz[0]) - MEASURED_Y_id.innerHTML = this.format_measured_angle(xyz[1]) - MEASURED_Z_id.innerHTML = this.format_measured_angle(xyz[2]) + let xyz + if(robot.rs) { xyz = robot.rs.xyz()[0] } //gets xyz array for joint 5} + else { xyz = ["N/A", "N/A", "N/A"] } + MEASURED_X_id.innerHTML = this.format_measured_meters(xyz[0]) + MEASURED_Y_id.innerHTML = this.format_measured_meters(xyz[1]) + MEASURED_Z_id.innerHTML = this.format_measured_meters(xyz[2]) } } else { @@ -195,7 +192,9 @@ var RobotStatusDialog = class RobotStatusDialog{ } static make_html_table_g0(robot_status, robot){ - let xyz = robot.rs.xyz()[0] //gets xyz array for joint 5 + let xyz + if(robot.rs) { xyz = robot.rs.xyz()[0] } //gets xyz array for joint 5 + else { xyz = ["N/A", "N/A", "N/A"] } return ( " Joint 1Joint 2Joint 3Joint 4Joint 5Joint 6Joint 7" + this.make_rs_row(robot_status, "MEASURED_ANGLE", "J1_MEASURED_ANGLE", "J2_MEASURED_ANGLE", "J3_MEASURED_ANGLE", "J4_MEASURED_ANGLE", "J5_MEASURED_ANGLE", "J6_MEASURED_ANGLE", "J7_MEASURED_ANGLE") + @@ -209,28 +208,32 @@ var RobotStatusDialog = class RobotStatusDialog{ //this.make_rs_row(robot_status, "PLAYBACK", "J1_PLAYBACK", "J2_PLAYBACK", "J3_PLAYBACK", "J4_PLAYBACK", "J5_PLAYBACK" ) + this.make_rs_row(robot_status, "SENT", "J1_SENT", "J2_SENT", "J3_SENT", "J4_SENT", "J5_SENT") + - "J5 MEASURED X" + this.format_measured_angle(xyz[0]) + - "J5 MEASURED Y" + this.format_measured_angle(xyz[1]) + - "J5 MEASURED Z" + this.format_measured_angle(xyz[2]) + + " J5 MEASURED X" + this.format_measured_meters(xyz[0]) + + "J5 MEASURED Y" + this.format_measured_meters(xyz[1]) + + "J5 MEASURED Z" + this.format_measured_meters(xyz[2]) + "" ) } static make_html_table_g1(robot_status, robot){ - let xyz = robot.rs.xyz()[0] //gets xyz array for joint 5 + let xyz + if(robot.rs) { xyz = robot.rs.xyz()[0]} //gets xyz array for joint 5 + else { xyz = ["N/A", "N/A", "N/A"] } return ( " Joint 1Joint 2Joint 3Joint 4Joint 5Joint 6Joint 7" + this.make_rs_row(robot_status, "MEASURED_ANGLE", "J1_MEASURED_ANGLE_G1", "J2_MEASURED_ANGLE_G1", "J3_MEASURED_ANGLE_G1", "J4_MEASURED_ANGLE_G1", "J5_MEASURED_ANGLE_G1", "J6_MEASURED_ANGLE_G1", "J7_MEASURED_ANGLE_G1") + this.make_rs_row(robot_status, "TORQUE", "J1_TORQUE_G1", "J2_TORQUE_G1", "J3_TORQUE_G1", "J4_TORQUE_G1", "J5_TORQUE_G1", "J6_TORQUE_G1", "J7_TORQUE_G1") + this.make_rs_row(robot_status, "VELOCITY", "J1_VELOCITY_G1", "J2_VELOCITY_G1", "J3_VELOCITY_G1", "J4_VELOCITY_G1", "J5_VELOCITY_G1", "J6_VELOCITY_G1", "J7_VELOCITY_G1") + - "J5 MEASURED X" + this.format_measured_angle(xyz[0]) + - "J5 MEASURED Y" + this.format_measured_angle(xyz[1]) + - "J5 MEASURED Z" + this.format_measured_angle(xyz[2]) + + " J5 MEASURED X" + this.format_measured_meters(xyz[0]) + + "J5 MEASURED Y" + this.format_measured_meters(xyz[1]) + + "J5 MEASURED Z" + this.format_measured_meters(xyz[2]) + "" ) } static make_html_table_g2(robot_status, robot){ - let xyz = robot.rs.xyz()[0] //gets xyz array for joint 5 + let xyz + if(robot.rs) { xyz = robot.rs.xyz()[0]} //gets xyz array for joint 5 + else { xyz = ["N/A", "N/A", "N/A"] } return ( " Joint 1Joint 2Joint 3Joint 4Joint 5Joint 6Joint 7" + this.make_rs_row(robot_status, "MEASURED_ANGLE", "J1_MEASURED_ANGLE_G2", "J2_MEASURED_ANGLE_G2", "J3_MEASURED_ANGLE_G2", "J4_MEASURED_ANGLE_G2", "J5_MEASURED_ANGLE_G2", "J6_MEASURED_ANGLE_G2", "J7_MEASURED_ANGLE_G2") + @@ -244,9 +247,9 @@ var RobotStatusDialog = class RobotStatusDialog{ //this.make_rs_row(robot_status, "PLAYBACK", "J1_PLAYBACK", "J2_PLAYBACK", "J3_PLAYBACK", "J4_PLAYBACK", "J5_PLAYBACK" ) + this.make_rs_row(robot_status, "SENT", "J1_SENT_G2", "J2_SENT_G2", "J3_SENT_G2", "J4_SENT_G2", "J5_SENT_G2") + - "J5 MEASURED X" + this.format_measured_angle(xyz[0]) + - "J5 MEASURED Y" + this.format_measured_angle(xyz[1]) + - "J5 MEASURED Z" + this.format_measured_angle(xyz[2]) + + "J5 MEASURED X" + this.format_measured_meters(xyz[0]) + + "J5 MEASURED Y" + this.format_measured_meters(xyz[1]) + + "J5 MEASURED Z" + this.format_measured_meters(xyz[2]) + "" ) } @@ -272,36 +275,42 @@ var RobotStatusDialog = class RobotStatusDialog{ return "" + result + "" } - static format_measured_angle(angle) { - if (angle == "no status") { return angle } - else { return to_fixed_smart(angle, 3) + "m" } + static format_measured_meters(angle) { + if (typeof(angle) === "number") { return to_fixed_smart(angle, 3) + "m" } + else { return angle } } static make_rs_row(robot_status, ...fields){ let result = "" let on_first = true let row_header = fields[0] - let sm = robot_status[Dexter.STATUS_MODE] + let sm = (robot_status ? robot_status[Dexter.STATUS_MODE] : 0) let do_decimal_processing = row_header != "" //let is_angle = fields[0].endsWith("ANGLE") //let degree_html = (is_angle ? "°" : "") - let val_units = "" for(let field of fields){ + let val_units = "" let val = (robot_status ? robot_status[Dexter[field]] : "no status") - if(field && field.includes("MEASURED_ANGLE") && (field !== "MEASURED_ANGLE")) { //exclude the header - if(sm > 0) { val = val / 3600 } //convert from degrees to arcseconds for all sm's except G0 - val_units = "°" - } - else if (field && field.includes("TORQUE") && ((field !== "TORQUE") || (field !== "MEASURED_TORQUE"))) { - if((val === undefined) || (val === null)) { val = "N/A" } //happens when sm === 0 for J1 thru 5 - else { - val = val / 1000000 - val_units = "Nm" //Newton-meter + if(robot_status) { + if(field && field.includes("MEASURED_ANGLE") && (field !== "MEASURED_ANGLE")) { //exclude the header + if(sm > 0) { val = val / 3600 } //convert from degrees to arcseconds for all sm's except G0 + val_units = "°" + } + else if (field && field.includes("TORQUE") && ((field !== "TORQUE") || (field !== "MEASURED_TORQUE"))) { + if((val === undefined) || (val === null)) { val = "N/A" } //happens when sm === 0 for J1 thru 5 + else { + val = val / 1000000 + val_units = "" //"Nm" //Newton-meter this is NOT Newton-meter its "dexter_units, ie speical Dynamixel units, 0 to 1023 + } + } + else if (field && field.includes("VELOCITY") && (field !== "VELOCITY")) { + val = val / 3600 //convert from arcseconds per second to degrees per second + val_units = "°/s" //degrees per scound } } - else if (field && field.includes("VELOCITY") && (field !== "VELOCITY")) { - val = val / 3600 //convert from arcseconds per second to degrees per second - val_units = "°/s" //degrees per scound + else { + val = "N/A" + val_units = "" } if(on_first) { result += "" + field + "" diff --git a/simulator/simutils.js b/simulator/simutils.js index e7de99fb..ec63a966 100644 --- a/simulator/simutils.js +++ b/simulator/simutils.js @@ -77,7 +77,7 @@ SimUtils = class SimUtils{ //ds_inst is an instance of DexterSim class. //robot_name example: "Dexter.dexter0" static render_multi(ds_instance, new_angles_dexter_units, robot_name, dur_in_ms=0){ //inputs in arc_seconds - console.log("render_multi passed: " + new_angles_dexter_units) + //console.log("render_multi passed: " + new_angles_dexter_units) if (Dexter.default.name === robot_name){ let dur_to_show = Math.round(dur_in_ms / 100) //in 10ths of seconds, rounded dur_to_show = "" + dur_to_show diff --git a/splash_screen.js b/splash_screen.js index c9f4e3b9..80aab5ba 100644 --- a/splash_screen.js +++ b/splash_screen.js @@ -61,7 +61,7 @@ var SplashScreen = class SplashScreen { x: 320, //same as dexter ui on purpose so that dui will "cover up" the splash screen. y: 100, width: 310, //380 is splash screen width 480, - height: 370, + height: 375, background_color: "#bae5fe", // pastel green: "#e7ffef", callback: "SplashScreen.show_splash_screen_cb", content: diff --git a/user_tools/dexter_user_interface2.js b/user_tools/dexter_user_interface2.js index cc694fd6..1c99792b 100644 --- a/user_tools/dexter_user_interface2.js +++ b/user_tools/dexter_user_interface2.js @@ -80,7 +80,7 @@ var dui2 = class dui2 { let name = ((platform === "dde") ? "dui2_for_" + dex.name : "dexter_user_interface2") //the job engine Job name must match the file name (sans .js") if (Job[name] && Job[name].is_active()) { //we're redefining the job so we want to make sure the //previous version is stopped first - if (Job[name].robot instanceof Dexter) {Job[name].robot.empty_instruction_queue_now() } + //if (Job[name].robot instanceof Dexter) {Job[name].robot.empty_instruction_queue_now() } Job[name].stop_for_reason("interrupted", "User is redefining this job.") setTimeout(function(){ dui2.make_job(true) }, 200) }