diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..bf434b68
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+Dexter Development Environment (DDE)
+Copyright (C) 2016-2024 Christopher Fry
+
+GPLv3
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 3.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
\ No newline at end of file
diff --git a/core/dextersim.js b/core/dextersim.js
index a5b8e39c..bb61901d 100644
--- a/core/dextersim.js
+++ b/core/dextersim.js
@@ -117,7 +117,15 @@ DexterSim = class DexterSim{
static array_buffer_to_oplet_array(arr_buff){
let str = this.array_buffer_to_string(arr_buff)
+ if(str.endsWith(";")) {
+ str = str.substring(0, str.length - 1) //cut off the semicolon on the end
+ }
let split_str = str.split(" ")
+ //if its a var length instruction, then an integer is in place of the oplet and the oplet is one later
+ let orig_oplet_maybe = split_str[Instruction.INSTRUCTION_TYPE]
+ if(!Robot.is_oplet(orig_oplet_maybe)) { //assume its an integer for a variable-length instruction
+ split_str.splice(Instruction.INSTRUCTION_TYPE, 1) //removes integer from var length array. makign it 1 shorter
+ }
let oplet_array = []
let oplet
for(let i = 0; i < split_str.length; i++) {
diff --git a/core/index.js b/core/index.js
index f8cd2912..36de9901 100644
--- a/core/index.js
+++ b/core/index.js
@@ -1,6 +1,5 @@
-global.dde_version = "3.8.19" //require("../package.json").version
-global.dde_release_date = "Jan 11, 2024" //require("../package.json").release_date
-
+global.dde_version = "3.9.0" //require("../package.json").version
+global.dde_release_date = "Feb 9, 2024" //require("../package.json").release_dat9
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/instruction_j_move.js b/core/instruction_j_move.js
index c7d01055..a9b43275 100644
--- a/core/instruction_j_move.js
+++ b/core/instruction_j_move.js
@@ -1,10 +1,10 @@
-/* Dexter_j_move_proposal.dde
-Written by: James Wigglesworth
-Started: 11/13/2023
-Modified: 11/27/2023
-
+/*
+//j_move_examples.dde
+//Written by: James Wigglesworth
+//Started: 11/13/2023
+//Modified: 2/5/2024
-Proposed new functions:
+New functions:
-Dexter.j_move()
-Dexter.j_reset()
-Dexter.j_set_peak_velocity()
@@ -21,8 +21,6 @@ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}),
Dexter.j_move([0, 0, 0, 0, 0, 0], {end_acceleration: [30, 0, 0, 0, 0, 0]}),
Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}),
-
-
Dexter.j_move([0, 30, 0, 0, 0, 0], {
end_velocity: [0, 0, 0, 0, 0, 0],
end_acceleration: [0, 0, 0, 0, 0, 0],
@@ -30,29 +28,42 @@ Dexter.j_move([0, 30, 0, 0, 0, 0], {
async: undefined, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value
}),
-
+load_files("Libraries/lib_j_moves.dde")
//Limits:
-var speed = [30, 30, 30, 30, 30, 30]
-var accel = [150, 150, 150, 150, 150, 150]
-var jerk = [3000, 3000, 3000, 3000, 3000, 3000]
-define_j_move_functions() //this is just to show the code for the Jobs first in this file
+var vel = [30, 30, 30, 30, 30, 30]
+var accel = [100, 100, 100, 100, 100, 100]
+var jerk = [1000, 1000, 1000, 1000, 1000, 1000]
+
+//Run once per boot up to initialize speed settings that work best for these examples.
+new Job({
+ name: "Init",
+ show_instructions: false,
+ do_list: [
+ init
+ ]
+})
//Job Examples:
-//Example 1 - simple motion
+//Example 0 - simple motion
new Job({
- name: "j_move_example_1",
+ name: "Example_0",
+ show_instructions: false,
+ do_list: [
+ Dexter.j_move([30, 0, 0, 0, 0]),
+ Dexter.j_move([0, 0, 0, 0, 0]),
+ ]
+})
+
+//Example 1 - simple motion loop (try stopping mid motion)
+new Job({
+ name: "Example_1",
show_instructions: false,
when_stopped: function(){
return Dexter.j_reset()
},
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
- Dexter.j_set_peak_acceleration(accel),
- Dexter.j_set_peak_jerk(jerk),
-
Control.loop(true, function(){
return [
Dexter.j_move([0, -45, 0, 0, 0]),
@@ -62,71 +73,188 @@ new Job({
]
})
-Example 1 oplet strings sent per loop
- j p 0,-162000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
- j p 0,162000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
-
-
+//Example 2 - Plotting J moves and how to use second argument of 'options'
+var example_2_data
+function record_example_2_data(){
+ return [
+ function(){
+ let obj = parse_g5(this.robot.robot_status)
+ obj.time = Date.now()*_ms - this.user_data.start_time
+ example_2_data.push(obj)
+ }
+ ]
+}
-//Example 2 - simple motion with duration
new Job({
- name: "j_move_example_2",
+ name: "Example_2",
show_instructions: false,
when_stopped: function(){
return Dexter.j_reset()
},
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
- Dexter.j_set_peak_acceleration(accel),
- Dexter.j_set_peak_jerk(jerk),
+ Dexter.j_move([0, 0, 0, 0, 0]),
+
+ function(){
+ example_2_data = []
+ this.user_data.start_time = Date.now()*_ms
+ },
+
+ Dexter.j_move([30, 0, 0, 0, 0], {
+ monitor_callback: record_example_2_data
+ }),
Dexter.j_move([0, 0, 0, 0, 0]),
- Control.loop(true, function(){
- return [
- Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}),
- Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}),
- Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}),
- ]
- })
+
+ function(){
+ //Plotting positional data:
+ plot_trajectory(example_2_data, {
+ title: "J_Move Position vs. Time",
+ data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "J_POSITION"],
+ joints_to_plot: [true, false, false, false, false, false],
+ y_axis: "Joint Angle (deg)"
+ })
+ },
+
+ function(){
+ //Plotting velocity data (notice the 'd', it can be used with other vairables too):
+ plot_trajectory(example_2_data, {
+ title: "J_Move Velocity vs. Time",
+ data_types: ["dMEASURED_ANGLE", "dSTEP_ANGLE", "J_VELOCITY"],
+ joints_to_plot: [true, false, false, false, false, false],
+ y_axis: "Joint Velocity (deg/s)"
+ })
+ },
+
+ function(){
+ //Plotting acceleration data (notice that 'd' can be applied multiple times):
+ plot_trajectory(example_2_data, {
+ title: "J_Move Acceleration vs. Time",
+ data_types: ["ddMEASURED_ANGLE", "ddSTEP_ANGLE", "J_ACCELERATION"], //you could add the
+ joints_to_plot: [true, false, false, false, false, false],
+ y_axis: "Joint Acceleration (deg/s^2)"
+ })
+ },
+
+
]
})
-Example 2 oplet strings sent per loop
- j p 0,-36000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 1;
- F 50;
- j p 0,-18000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 1;
- F 50;
- j p 0,36000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 1;
- F 50;
+//Example 3 - Setting 'peak' values
+new Job({
+ name: "Example_3",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ do_list: [
+ init, // sets speeds to default values
+ Dexter.j_move([0, 0, 0, 0, 0]),
+ function(){
+ example_3_data = []
+ this.user_data.start_time = Date.now()*_ms
+ },
+
+ //Default settings:
+ Dexter.j_move([30, 0, 0, 0, 0], {
+ peak_velocity: [30, 30, 30, 30, 30, 30],
+ peak_acceleration: [100, 100, 100, 100, 100, 100],
+ peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000],
+ monitor_callback: record_example_3_data
+ }),
+
+ //2x higher velocity:
+ Dexter.j_move([60, 0, 0, 0, 0], {
+ peak_velocity: [40, 30, 30, 30, 30, 30],
+ peak_acceleration: [100, 100, 100, 100, 100, 100],
+ peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000],
+ monitor_callback: record_example_3_data
+ }),
+
+ //3x higher acceleration:
+ Dexter.j_move([90, 0, 0, 0, 0], {
+ peak_velocity: [30, 30, 30, 30, 30, 30],
+ peak_acceleration: [3*100, 100, 100, 100, 100, 100],
+ peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000],
+ monitor_callback: record_example_3_data
+ }),
+
+ //30x higher jerk:
+ Dexter.j_move([120, 0, 0, 0, 0], {
+ peak_velocity: [30, 30, 30, 30, 30, 30],
+ peak_acceleration: [100, 100, 100, 100, 100, 100],
+ peak_jerk: [30*1000, 1000, 1000, 1000, 1000, 1000],
+ monitor_callback: record_example_3_data
+ }),
+
+ //higher everything:
+ Dexter.j_move([150, 0, 0, 0, 0], {
+ peak_velocity: [40, 30, 30, 30, 30, 30],
+ peak_acceleration: [3*100, 100, 100, 100, 100, 100],
+ peak_jerk: [30*1000, 1000, 1000, 1000, 1000, 1000],
+ monitor_callback: record_example_3_data
+ }),
+
+ //Go to Home at normal settings:
+ Dexter.j_move([0, 0, 0, 0, 0], {
+ peak_velocity: [30, 30, 30, 30, 30, 30],
+ peak_acceleration: [100, 100, 100, 100, 100, 100],
+ peak_jerk: [1000, 1000, 1000, 1000, 1000, 1000],
+ }),
+
+ function(){
+ //Plotting positional data:
+ plot_trajectory(example_3_data, {
+ title: "J_Move Position vs. Time",
+ data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "J_POSITION"],
+ joints_to_plot: [true, false, false, false, false, false],
+ y_axis: "Joint Angle (deg)"
+ })
+ },
+
+ function(){
+ //Plotting velocity data (notice the 'd', it can be used with other vairables too):
+ plot_trajectory(example_3_data, {
+ title: "J_Move Velocity vs. Time",
+ data_types: ["dMEASURED_ANGLE", "dSTEP_ANGLE", "J_VELOCITY"],
+ joints_to_plot: [true, false, false, false, false, false],
+ y_axis: "Joint Velocity (deg/s)"
+ })
+ },
+
+
+ //function(){
+ //Plotting acceleration data (notice that 'd' can be applied multiple times):
+ // plot_trajectory(example_3_data, {
+ // title: "J_Move Acceleration vs. Time",
+ // data_types: ["ddMEASURED_ANGLE", "ddSTEP_ANGLE", "J_ACCELERATION"], //
+ // joints_to_plot: [true, false, false, false, false, false],
+ // y_axis: "Joint Acceleration (deg/s^2)"
+ // })
+ // },
+ ]
+})
+
+var example_3_data
+function record_example_3_data(){
+ return [
+ function(){
+ let obj = parse_g5(this.robot.robot_status)
+ obj.time = Date.now()*_ms - this.user_data.start_time
+ example_3_data.push(obj)
+ }
+ ]
+}
-//Example 3 - Motion through a waypoint
+//Example 4 - Setting 'end' values
new Job({
- name: "j_move_example_3",
+ name: "Example_4",
show_instructions: false,
when_stopped: function(){
return Dexter.j_reset()
},
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
- Dexter.j_set_peak_acceleration(accel),
- Dexter.j_set_peak_jerk(jerk),
-
Control.loop(true, function(){
return [
Dexter.j_move([-30, -30, 0, 0, 0, 0]),
@@ -137,88 +265,28 @@ new Job({
]
})
-Example 3 oplet strings sent per loop
- j p -108000,-108000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
- j p 0,0,0,0,0,0;
- j v 108000,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- j p 108000,0,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
-
-
-//Example 4 - Sync vs. Async calls (This won't work until we impliment 'F' to work for j moves)
+//Example 5 - Controlling speeds with 'duration'
new Job({
- name: "j_move_example_4",
+ name: "Example_5",
show_instructions: false,
when_stopped: function(){
return Dexter.j_reset()
},
- user_data: {
- move_count: 0,
- },
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
- Dexter.j_set_peak_acceleration(accel),
- Dexter.j_set_peak_jerk(jerk),
-
+ Dexter.j_move([0, 0, 0, 0, 0]),
Control.loop(true, function(){
return [
- function(){
- this.user_data.move_count++
- out("Synchronous j_move " + this.user_data.move_count + " starting...")
- this.user_data.start_time = Date.now()
- },
- Dexter.j_move([0, -30, 0, 0, 0, 0]),
- function(){
- let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
- out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration)
- },
-
- function(){
- out("Asynchronous j_move " + this.user_data.move_count + " starting...")
- this.user_data.start_time = Date.now()
- },
- Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}),
- function(){
- let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
- out("Asynchronous j_move " + this.user_data.move_count + " completed in " + duration)
- },
-
- function(){
- this.user_data.move_count++
- out("Synchronous j_move " + this.user_data.move_count + " starting...")
- this.user_data.start_time = Date.now()
- },
- Dexter.j_move([0, 0, 0, 0, 0, 0]),
- function(){
- let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
- out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration)
- },
-
+ Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}),
+ Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}),
+ Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}),
]
})
]
})
-Example 4 oplet strings sent per loop
- j p -108000,-108000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
- j p 0,0,0,0,0,0;
- j v 108000,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
-
-
-//Example 5 - Sync calls while moving (This won't work until we impliment sync_delay for 'F')
+//Example 6 - Asynchronous j_moves (watch when the job button turns purple)
new Job({
- name: "j_move_example_5",
+ name: "Example_6",
show_instructions: false,
when_stopped: function(){
return Dexter.j_reset()
@@ -227,39 +295,142 @@ new Job({
move_count: 0,
},
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
- Dexter.j_set_peak_acceleration(accel),
- Dexter.j_set_peak_jerk(jerk),
+ Dexter.j_move([0, -30, 0, 0, 0, 0], {async: false}),
+ ]
+})
+//Example 7 - Synchronous j_moves during motion (listen for beeps mid-motion)
+new Job({
+ name: "Example_7",
+ show_instructions: false,
+ inter_do_item_dur: 0,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ user_data: {
+ move_count: 0,
+ },
+ do_list: [
Control.loop(true, function(){
return [
- Dexter.j_move([0, -30, 0, 0, 0, 0]),
- Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0], async: false, sync_delay: -0.1}),
- function(){
- this.user_data.move_count++
- out("Midpoint " + this.user_data.move_count)
- },
Dexter.j_move([0, 30, 0, 0, 0, 0]),
+ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, -30, 0, 0, 0, 0]}),
+ make_sound,
+ Dexter.j_move([0, -30, 0, 0, 0, 0]),
+ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0]}),
+ make_sound
]
})
]
})
-Example 5 oplet strings sent per loop
- j p 0,-108000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
- j p 0,0,0,0,0,0;
- j v 0,108000,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F -100;
- j p 0,108000,0,0,0,0;
- j v 0,0,0,0,0,0;
- j a 0,0,0,0,0,0 ?;
- F 50;
+function make_sound(){
+ beep({dur: 0.1, frequency: 600, volume: 1})
+ this.user_data.move_count++
+ out("Midpoint " + this.user_data.move_count)
+}
+//Example 8 - Using 'this.user_data.j_move' to track which j_move it's currently on, even with async = true
+var example_8_data
+var delay = 0.5
+new Job({
+ name: "Example_8",
+ show_instructions: false,
+ inter_do_item_dur: 0,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ do_list: [
+ function(){
+ clear_output()
+ example_8_data = []
+ },
+ Dexter.j_move([0, 0, 0, 0, 0]),
+ Control.wait_until(1),
+
+ function(){this.user_data.start_time = Date.now() * _ms},
+ Dexter.j_move([30, 0, 0, 0, 0], {async: true}),
+ Dexter.j_move([60, 0, 0, 0, 0], {async: true}),
+ loop_for_dur(delay, record_data_example_8),
+ Dexter.j_move([90, 0, 0, 0, 0], {monitor_callback: record_data_example_8}),
+
+ function(){
+ inspect(this.user_data.j_move)
+ },
+
+ function(){
+ let joints_to_plot = [true, false, false, false, false, false]
+ let plot_data = []
+ let time_stack = this.user_data.j_move.time_stack
+ let ys = [0, 40]
+ for(let i = 0; i < time_stack.length; i++){
+ let label_str = ""
+ let j_cmd = this.user_data.j_move.cmd_stack[i]
+ for(let j = 0; j < joints_to_plot.length; j++){
+ if(joints_to_plot[j]){
+ if(j !== 0){
+ label_str += "
"
+ }
+ label_str += "J" + (j+1) + ": [p:" + j_cmd.p[j] + ",v:" + j_cmd.v[j] + ",a:" + j_cmd.a[j] + "]"
+ }
+ }
+ plot_data.push({
+ type: "scatter",
+ name: label_str,
+ //mode: "markers",
+ mode: "lines",
+ x: [time_stack[i], time_stack[i]],
+ y: ys,
+ marker: {
+ width: 1,
+ }
+ })
+ }
+
+ let times = []
+ let stack_idxs = []
+ let num_sent_cmds = []
+ for(let i = 0; i < example_8_data.length; i++){
+ times.push(example_8_data[i].time)
+ stack_idxs.push(example_8_data[i].cur_stack_idx)
+ num_sent_cmds.push(example_8_data[i].num_sent_cmds)
+ }
+
+ plot_data.push({
+ type: "scatter",
+ name: "this.user_data.j_move.cur_stack_idx",
+ //mode: "markers",
+ mode: "lines",
+ x: times,
+ y: stack_idxs,
+ marker: {
+ width: 1,
+ }
+ })
+
+ plot_trajectory(example_8_data, {
+ title: "J Move Completion Prediction, Delay = " + delay + "s",
+ data_types: ["dMEASURED_ANGLE", "J_VELOCITY"],
+ joints_to_plot: joints_to_plot,
+ y_axis: "Joint Velocity (deg/s)",
+ additional_plot_data: plot_data
+ })
+ }
+ ]
+})
+
+function record_data_example_8(){
+ return [
+ function(){
+ let obj = parse_g5(this.robot.robot_status)
+ obj.time = Date.now()*_ms - this.user_data.start_time
+ obj.num_sent_cmds = this.user_data.j_move.num_sent_cmds
+ obj.cur_stack_idx = this.user_data.j_move.cur_stack_idx
+
+ example_8_data.push(obj)
+ }
+ ]
+}
new Job({
name: "stop_j_move",
@@ -275,109 +446,465 @@ new Job({
inter_do_item_dur: 0,
show_instructions: false,
do_list: [
- j_set_hardware_limits(),
- Dexter.j_set_peak_velocity(speed),
+ init,
+ Dexter.j_move([0, 0, 0, 0, 0])
+ ]
+})
+
+new Job({
+ name: "Print_Strs",
+ robot: Brain.brain0,
+ inter_do_item_dur: 0,
+ show_instructions: false,
+ do_list: [
+ function(){
+ let keys = Job.all_names
+ let completed_jobs = []
+ debugger
+ for(let i = 0; i < keys.length; i++){
+ if(Job[keys[i]].status_code === "completed"){
+ completed_jobs.push({
+ job_name: keys[i],
+ stop_time: Job[keys[i]].stop_time
+ })
+ }
+ }
+ if(completed_jobs.length === 0){
+ out("No completed jobs found.
Button must be purple.
Don't 'Eval' after completion.")
+ return
+ }
+ completed_jobs = completed_jobs.sort(function(a, b){
+ let date_a = Date(a.stop_time)
+ let date_b = Date(b.stop_time)
+ return date_a - date_b
+ })
+
+ let last_job = Job[completed_jobs[0].job_name]
+ out(last_job + " sent instruction strings:")
+ inspect(last_job.sent_instructions_strings)
+ }
+ ]
+})
+
+function init(){
+ return [
+ j_set_hardware_limits(), //in case they aren't already set in Defaults.make_ins
+ Dexter.j_set_peak_velocity(vel),
Dexter.j_set_peak_acceleration(accel),
Dexter.j_set_peak_jerk(jerk),
+ ]
+}
- Dexter.j_move([0, 0, 0, 0, 0])
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Ignore all code below here:
+
+function plot_trajectory(data, options = {
+ data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK"],
+ joints_to_plot: [true, true, true, true, true, true],
+ x_axis: "Time (s)",
+ y_axis: "Joint Angle (deg)",
+ title: "Trajectory",
+ absolute_values: false,
+ additional_plot_data: []
+}){
+ let default_options = {
+ data_types: ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK"],
+ joints_to_plot: [true, true, true, true, true, true],
+ x_axis: "Time (s)",
+ y_axis: "Joint Angle (deg)",
+ title: "Trajectory",
+ absolute_values: false,
+ additional_plot_data: []
+ }
+ let keys = Object.keys(options)
+ for(let i = 0; i < keys.length; i++){
+ if(default_options[keys[i]] === undefined){
+ dde_error("The key of '" + keys[i] + "' is not valid for plot_trajectory()")
+ }
+ default_options[keys[i]] = options[keys[i]]
+ }
+
+ let color_array = [
+ "red", "green", "blue", "magenta", "cyan", "orange", "brown", "lime", "purple",
+ rgb(153,195,167), rgb(92,50,91), rgb(240,20,120), rgb(200,134,65), rgb(93,51,93),
+ rgb(169,246,74), rgb(81,150,118), rgb(64,71,239), rgb(110,165,222), rgb(133,85,217),
+ rgb(77,29,17), rgb(43,200,132), rgb(160,212,211), rgb(74,249,166), rgb(90,237,21),
+ rgb(130,186,240), rgb(118,46,24), rgb(143,226,182), rgb(35,146,158), rgb(88,97,161)
]
-})
-*/
- //stops Dexter from moving where ever it is and leaves it there.
- Dexter.j_reset = function(){
- return make_ins("j")
+
+ if(!Array.isArray(data)){
+ data = obj_of_arrs_to_arr_objs(data)
}
- Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){
- let cmd = []
- for(let i = 0; i < velocity.length; i++){
- cmd.push(make_ins("S", "JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec)))
+ let plot_data = []
+ let T = 0
+
+ let time_range = [Infinity, -Infinity]
+ for(let i = 0; i < default_options.data_types.length; i++){
+ let key = default_options.data_types[i]
+ let d_count = get_d_chars(key)
+ key = key.slice(d_count)
+ let values = []
+ let times = []
+ for(let k = 0; k < data.length; k++){
+ values.push(data[k][key])
+ if(data[k].time === undefined && data[k].times){
+ times.push(data[k].times)
+ }else if(data[k].time && data[k].times === undefined){
+ times.push(data[k].time)
+ }else{
+ out("data[k]:")
+ inspect(data[k])
+ dde_error("plot_trajectory() given incorrectly formatted time feild")
+ }
+ }
+
+ let time = Vector.min(times)
+ if(time < time_range[0]){
+ time_range[0] = time
+ }
+ time = Vector.max(times)
+ if(time > time_range[1]){
+ time_range[1] = time
+ }
+
+ let values_T = Vector.transpose(values)
+ for(let k = 0; k < values_T.length; k++){
+ if(default_options.joints_to_plot[k] === false){
+ continue
+ }
+ let ds = ""
+ for(let l = 0; l < d_count; l++){
+ ds += "d"
+ values_T[k] = derive(times, values_T[k])
+ }
+
+ if(default_options.absolute_values){
+ values_T[k] = Vector.abs(values_T[k])
+ }
+
+ plot_data.push({
+ type: "scatter",
+ name: "J" + (k+1) + " " + ds + key,
+ //mode: "markers",
+ mode: "lines",
+ x: times,
+ y: values_T[k],
+ marker: {
+ width: 1,
+ //color: get_joint_color([T++])
+ color: color_array[T++]
+ }
+ })
}
- return cmd
}
- Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){
- let cmd = []
- for(let i = 0; i < accel.length; i++){
- cmd.push(make_ins("S", "JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec)))
+ for(let i = 0; i < default_options.additional_plot_data.length; i++){
+ let obj = default_options.additional_plot_data[i]
+ if(obj.marker.color === undefined){
+ obj.marker.color = color_array[T++]
}
- return cmd
+ plot_data.push(obj)
}
- Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){
- let cmd = []
- for(let i = 0; i < jerk.length; i++){
- cmd.push(make_ins("S", "JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec)))
+ Plot.show(
+ undefined,
+ plot_data,
+ {
+ title: default_options.title,
+ xaxis: {title: {text: options.x_axis}},
+ yaxis: {title: {text: options.y_axis}},
+ },
+ undefined,
+ {
+ title: default_options.title,
+ x: 580,
+ y: 40,
}
- return cmd
+ )
+}
+
+function derive(times, values){
+ let result = []
+ for(let i = 0; i < values.length-1; i++){
+ result.push((values[i+1] - values[i]) / (times[i+1] - times[i]))
}
+ return result
+}
+function get_d_chars(string){
+ let count = 0
+ for(let i = 0; i < string.length; i++){
+ if(string[i] === "d"){
+ count++
+ }else{
+ break
+ }
+ }
+ return count
+}
- //goal_position is an array of angles, its lengths should be the same as the
- //measured angles that we compare to.
- //Truncate rs[Dexter.J1_MEASURED_ANGLE], based on goal_position length
- Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){
- return Control.loop(true, function() {
- let cmd = []
-
- let rs = this.robot.robot_status
- let status_mode = rs[Dexter.STATUS_MODE]
- let meas
- if (status_mode === 0) {
- meas = [
- rs[Dexter.J1_MEASURED_ANGLE],
- rs[Dexter.J2_MEASURED_ANGLE],
- rs[Dexter.J3_MEASURED_ANGLE],
- rs[Dexter.J4_MEASURED_ANGLE],
- rs[Dexter.J5_MEASURED_ANGLE],
- ]
- } else if (status_mode === 5) {
- meas = [
- rs[Dexter.g5_J1_MEASURED_ANGLE],
- rs[Dexter.g5_J2_MEASURED_ANGLE],
- rs[Dexter.g5_J3_MEASURED_ANGLE],
- rs[Dexter.g5_J4_MEASURED_ANGLE],
- rs[Dexter.g5_J5_MEASURED_ANGLE],
+function loop_for_dur(duration, callback = function(){}){
+ return [
+ function(){this.user_data.loop_for_dur_start_time = Date.now()*_ms},
+ Control.loop(true, function(){
+ let dur = Date.now()*_ms - this.user_data.loop_for_dur_start_time
+ if(dur >= duration){
+ return Control.break
+ }else{
+ return [
+ make_ins("g 5"),
+ callback
]
- meas = Vector.multiply(meas, _arcsec)
- }
-
- let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5))))
- cmd.push(callback)
- if (error < tolerance) {
- cmd.push(Control.break())
- } else {
- //cmd.push(Dexter.get_robot_status())
- cmd.push(make_ins("g", 5))
}
- return cmd
})
+ ]
+}
+
+function parse_g5(rs){
+ let result = {}
+ let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK", "J_temp_reserved_2"]
+ for(let j = 0; j < keys.length; j++){
+ result[keys[j]] = []
+ for(let i = 0; i < 6; i++){
+ let key = "g5_J" + (i+1) + "_" + keys[j]
+ result[keys[j]].push(rs[Dexter[key]] * _arcsec)
+ }
+ }
+ result.JTQ_DUR = rs[Dexter.g5_JTQ_DUR] / 100000
+ return result
+}
+
+
+
+
+function arr_of_objs_to_obj_of_arrs(arr_obj){
+ //array of objects to object of arrays
+ //[{key_1: val, key_2: val}, {key_1: val, key_2: val}] -> {key_1: [], key_2: []}
+
+ let keys = Object.keys(arr_obj[0])
+ let obj_arr = {}
+ for(let i = 0; i < keys.length; i++){
+ let key = keys[i]
+ obj_arr[key] = []
+ for(let j = 0; j < arr_obj.length; j++){
+ obj_arr[key].push(arr_obj[j][key])
+ }
+ }
+ return obj_arr
+}
+
+function obj_of_arrs_to_arr_objs(obj_arr){
+ //object of arrays to array of objects
+ //{key_1: [], key_2: []} -> [{key_1: val, key_2: val}, {key_1: val, key_2: val}]
+
+ let keys = Object.keys(obj_arr)
+ let arr_obj = []
+ for(let i = 0; i < obj_arr[keys[0]].length; i++){
+ let obj = {}
+ for(let j = 0; j < keys.length; j++){
+ let key = keys[j]
+ obj[key] = obj_arr[key][i]
+ }
+ arr_obj.push(obj)
}
+ return arr_obj
+}
+
+function get_joint_color(J_idx, shade_scale = 0){
+ let color_array = ["red", "green", "blue", "magenta", "cyan", "orange"]
+ if(shade_scale > 1 || -1 > shade_scale){
+ dde_error("get_joint_color() shade_scale must be -1 to 1.
It was passed a shade_scale of: "+ shade_scale)
+ }
+ let color_a
+ if(typeof J_idx === "number"){
+ if(J_idx >= color_array.length){
+ dde_error("get_joint_color() J_idx must be 0 to " + (color_array.length-1) + ".
It was passed a J_idx of: "+ J_idx)
+ }
+ color_a = color_array[J_idx]
+ }else if(typeof J_idx === "string"){
+ color_a = color_str_to_arr(J_idx)
+ }else if(J_idx === undefined){
+ color_a = [Math.round(Math.random()*255), Math.round(Math.random()*255), Math.round(Math.random()*255)]
+ }
+ let color_b
+ if(shade_scale < 0){
+ color_b = [0, 0, 0]
+ shade_scale = Math.abs(shade_scale)
+ }else{
+ color_b = [240, 240, 240]
+ }
+
+ let color = interp_color(color_a, color_b, shade_scale)
+ return color
+}
+
+//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed.
+function j_set_hardware_limits(){
+ //I'll come back and make this have args for everything later
+ return [
+ "S, JointHardwareMaxSpeed, 1, 28800000;",
+ "S, JointHardwareMaxSpeed, 2, 28800000;",
+ "S, JointHardwareMaxSpeed, 3, 28800000;",
+ "S, JointHardwareMaxSpeed, 4, 432000000;",
+ "S, JointHardwareMaxSpeed, 5, 432000000;",
+ "S, JointHardwareMaxSpeed, 6, 576000000;",
+
+ "S, JointHardwareMaxAcceleration, 1, 2880000000;",
+ "S, JointHardwareMaxAcceleration, 2, 2880000000;",
+ "S, JointHardwareMaxAcceleration, 3, 2880000000;",
+ "S, JointHardwareMaxAcceleration, 4, 432000000;",
+ "S, JointHardwareMaxAcceleration, 5, 432000000;",
+ "S, JointHardwareMaxAcceleration, 6, 576000000;",
+
+ "S, JointHardwareMaxJerk, 1, 32400000000;",
+ "S, JointHardwareMaxJerk, 2, 32400000000;",
+ "S, JointHardwareMaxJerk, 3, 32400000000;",
+ "S, JointHardwareMaxJerk, 4, 324000000000;",
+ "S, JointHardwareMaxJerk, 5, 324000000000;",
+ "S, JointHardwareMaxJerk, 6, 324000000000;"
+ ]
+}
+*/
- Dexter.j_move = function(j_angles = [], options = {
- end_velocity: [0, 0, 0, 0, 0, 0],
- end_acceleration: [0, 0, 0, 0, 0, 0],
- duration: "?",
- async: false,
- sync_delay: 0.05,
- peak_velocity: undefined,
- peak_acceleration: undefined,
- peak_jerk: undefined,
- monitor_callback: function(){}
- }){
+Dexter.g5_JTQ_DUR = 58
+Dexter.j_reset = function(){
+ return function(){
+ return [
+ make_ins("j"),
+ Dexter.j_init,
+ ]
+ }
+}
+
+Dexter.j_init = function(){
+ return function(){
+ this.user_data.j_move = {}
+ this.user_data.j_move.j_reset_start_time = Date.now() * _ms
+ this.user_data.j_move.time_stack = []
+ let experimental_fudge_factor = 0.09//0.06693
+ let replay_delay = 0.05
+ this.user_data.j_move.dur_sum = replay_delay + experimental_fudge_factor
+
+ this.user_data.j_move.cmd_stack = []
+ this.user_data.j_move.cur_stack_idx = 0
+ this.user_data.j_move.num_sent_cmds = -1
+
+ return "g 5"
+ }
+}
+
+Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){
+ let cmd = []
+ for(let i = 0; i < velocity.length; i++){
+ cmd.push("S JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec) + ";")
+ }
+ return cmd
+}
+
+Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){
+ let cmd = []
+ for(let i = 0; i < accel.length; i++){
+ cmd.push("S JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec) + ";")
+ }
+ return cmd
+}
+
+Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){
+ let cmd = []
+ for(let i = 0; i < jerk.length; i++){
+ cmd.push("S JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec) + ";")
+ }
+ return cmd
+}
+
+Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){
+ return Control.loop(true, function(){
+ let cmd = []
+
+ let rs = this.robot.robot_status
+ let status_mode = rs[Dexter.STATUS_MODE]
+ let meas
+ if(status_mode === 0){
+ meas = [
+ rs[Dexter.J1_MEASURED_ANGLE],
+ rs[Dexter.J2_MEASURED_ANGLE],
+ rs[Dexter.J3_MEASURED_ANGLE],
+ rs[Dexter.J4_MEASURED_ANGLE],
+ rs[Dexter.J5_MEASURED_ANGLE],
+ ]
+ }else if(status_mode === 5){
+ meas = [
+ rs[Dexter.g5_J1_MEASURED_ANGLE],
+ rs[Dexter.g5_J2_MEASURED_ANGLE],
+ rs[Dexter.g5_J3_MEASURED_ANGLE],
+ rs[Dexter.g5_J4_MEASURED_ANGLE],
+ rs[Dexter.g5_J5_MEASURED_ANGLE],
+ ]
+ meas = Vector.multiply(meas, _arcsec)
+ }
+
+ let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5))))
+ cmd.push(callback)
+ if(error < tolerance){
+ cmd.push(Control.break())
+ }else{
+ //cmd.push(Dexter.get_robot_status())
+ cmd.push(make_ins("g 5"))
+ }
+ return cmd
+ })
+}
+
+Dexter.j_wait_until_idx = function(idx, sync_delay, callback){
+ //This allows j moves to be synchronous via jtq remaining duration
+ return Control.loop(true, function(){
+ let cmd = []
+ let dur = Date.now() * _ms - this.user_data.j_move.j_reset_start_time
+
+ for(let i = this.user_data.j_move.cur_stack_idx; i < this.user_data.j_move.time_stack.length; i++){
+ if(dur >= this.user_data.j_move.time_stack[this.user_data.j_move.cur_stack_idx]){
+ this.user_data.j_move.cur_stack_idx++
+ }
+ }
+
+ if(dur >= sync_delay + this.user_data.j_move.time_stack[idx]){
+ return Control.break()
+ }
+
+ cmd.push(make_ins("g 5"))
+ cmd.push(callback)
+ return cmd
+ })
+}
+
+Dexter.j_move = function(j_angles = [], options = {
+ peak_velocity: undefined,
+ peak_acceleration: undefined,
+ peak_jerk: undefined,
+ end_velocity: [0, 0, 0, 0, 0, 0],
+ end_acceleration: [0, 0, 0, 0, 0, 0],
+ duration: "?",
+ async: false,
+ sync_delay: 0.05,
+ in_motion_sync_delay: -0.2,
+ monitor_callback: function(){}
+}){
+ return function(){
+ let cmd = []
let default_options = {
- end_velocity: [0, 0, 0, 0, 0, 0],
- end_acceleration: [0, 0, 0, 0, 0, 0],
- duration: "?", //'?' means get there as fast as possible
+ peak_velocity: undefined, // peak velocity allowed during this motion. 'undefined' means keep previous value.
+ peak_acceleration: undefined, // peak acceleration allowed during this motion. 'undefined' means keep previous value.
+ peak_jerk: undefined, // peak jerk allowed during this motion. 'undefined' means keep previous value.
+
+ end_velocity: [0, 0, 0, 0, 0, 0], // velocity that this motion ends at. Start velocity comes from previous j move's end_velocity.
+ end_acceleration: [0, 0, 0, 0, 0, 0], // velocity that this motion ends at. Start acceleration comes from previous j move's end_acceleration.
+ duration: "?", //'?' means get there as fast as possible given peak values
async: false, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value
sync_delay: 0.05, //time from JtQ completion. Negative value means 'time before completion'.
-
- peak_velocity: undefined,
- peak_acceleration: undefined,
- peak_jerk: undefined,
+ in_motion_sync_delay: -0.2, //time from JtQ completion. Is only used if end vel/accel are not 0.
monitor_callback: function(){} //This function will get called over and over waiting for movement to complete. It returns to the do_list.
}
@@ -387,6 +914,18 @@ new Job({
}else if(j_angles.length !== 6){
dde_error("Dexter.j_move() was passed: " + j_angles + "
It must have 5 or 6 joint angles as arguments")
}
+ if(this.user_data.j_move === undefined){
+ //This means it's the first j move in a job
+ this.user_data.j_move = {}
+ this.user_data.j_move.j_reset_start_time = Date.now() * _ms
+ this.user_data.j_move.time_stack = []
+ this.user_data.j_move.dur_sum = 0.05
+
+ this.user_data.j_move.cmd_stack = []
+ this.user_data.j_move.cur_stack_idx = 0
+ this.user_data.j_move.num_sent_cmds = -1
+ cmd.push(Dexter.j_reset)
+ }
let option_keys = Object.keys(options)
let default_option_keys = Object.keys(default_options)
@@ -397,14 +936,6 @@ new Job({
default_options[option_keys[i]] = options[option_keys[i]]
}
- /*
- if(default_options.async === false || Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){
- default_options.async = false
- }else{
- default_options.async = true
- }
- */
-
if(default_options.end_velocity.length === 5){
default_options.end_velocity.push(0)
}
@@ -413,9 +944,6 @@ new Job({
default_options.end_acceleration.push(0)
}
-
- let cmd = []
-
if(default_options.peak_velocity !== undefined){
cmd.push(Dexter.j_set_peak_velocity(default_options.peak_velocity))
}
@@ -426,22 +954,47 @@ new Job({
cmd.push(Dexter.j_set_peak_jerk(default_options.peak_jerk))
}
- cmd.push(make_ins("j", "p", ...Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0)))
- cmd.push(make_ins("j", "v", ...Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0)))
- cmd.push(make_ins("j", "a", ...Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0), default_options.duration))
+ cmd.push(make_ins("j p " + Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0)))
+ cmd.push(make_ins("j v " + Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0)))
+ cmd.push(make_ins("j a " + Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0) + " " + default_options.duration))
+ cmd.push(function(){
+ //Algorithm for syncing j moves via time information
+ this.user_data.j_move.num_sent_cmds++
+ let jtq_dur = this.robot.robot_status[Dexter.g5_JTQ_DUR] * 0.00001
+
+ //new method:
+ let delta_time = Date.now()*_ms + jtq_dur - this.user_data.j_move.j_reset_start_time
+ this.user_data.j_move.time_stack.push(delta_time)
+
+ this.user_data.j_move.cmd_stack.push({
+ p: j_angles,
+ v: default_options.end_velocity,
+ a: default_options.end_acceleration,
+ d: default_options.duration
+ })
+ })
if(default_options.async === false){
- //Eventually this will be the F oplet but for now:
- cmd.push(Dexter.wait_until_measured_angles(j_angles, undefined, options.monitor_callback))
- cmd.push(Control.wait_until(options.sync_delay))
- //cmd.push(make_ins("F", Math.round(default_options.sync_delay/_ms))) //This may or may not become a different oplet specifically for j moves. Units could become seconds too.
- }
+
+
+ //Old Method:
+ if(Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){
+ //If coming to a stop reset
+ //This prevents the j queue underruning or getting too large
+
+ cmd.push(function(){return Dexter.j_wait_until_idx(this.user_data.j_move.num_sent_cmds, default_options.sync_delay, default_options.monitor_callback)})
+ cmd.push(Dexter.j_reset)
+ }else{
+ cmd.push(function(){return Dexter.j_wait_until_idx(this.user_data.j_move.num_sent_cmds, default_options.in_motion_sync_delay, default_options.monitor_callback)})
+ }
+ }
return cmd
}
+}
function init_g5_robot_status_indexes() {
- let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_temp_reserved_1", "J_temp_reserved_2"]
+ let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_JERK", "J_temp_reserved_2"]
for (let i = 0; i < 6; i++) {
for (let j = 0; j < keys.length; j++) {
let key = "g5_J" + (i + 1) + "_" + keys[j]
@@ -450,33 +1003,5 @@ function init_g5_robot_status_indexes() {
}
}
}
-init_g5_robot_status_indexes()
-
-/*
-//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed.
-function j_set_hardware_limits(){
- //I'll come back and make this have args for everything later
- return [
- "S, JointHardwareMaxSpeed, 1, 2880000;",
- "S, JointHardwareMaxSpeed, 2, 2880000;",
- "S, JointHardwareMaxSpeed, 3, 2880000;",
- "S, JointHardwareMaxSpeed, 4, 4320000;",
- "S, JointHardwareMaxSpeed, 5, 4320000;",
- "S, JointHardwareMaxSpeed, 6, 5760000;",
-
- "S, JointHardwareMaxAcceleration, 1, 28800000;",
- "S, JointHardwareMaxAcceleration, 2, 28800000;",
- "S, JointHardwareMaxAcceleration, 3, 28800000;",
- "S, JointHardwareMaxAcceleration, 4, 43200000;",
- "S, JointHardwareMaxAcceleration, 5, 43200000;",
- "S, JointHardwareMaxAcceleration, 6, 57600000;",
-
- "S, JointHardwareMaxJerk, 1, 324000000;",
- "S, JointHardwareMaxJerk, 2, 324000000;",
- "S, JointHardwareMaxJerk, 3, 324000000;",
- "S, JointHardwareMaxJerk, 4, 324000000;",
- "S, JointHardwareMaxJerk, 5, 324000000;",
- "S, JointHardwareMaxJerk, 6, 324000000;"
- ]
-}*/
+init_g5_robot_status_indexes()
\ No newline at end of file
diff --git a/core/instruction_j_move_dec_2023.js b/core/instruction_j_move_dec_2023.js
new file mode 100644
index 00000000..c7d01055
--- /dev/null
+++ b/core/instruction_j_move_dec_2023.js
@@ -0,0 +1,482 @@
+/* Dexter_j_move_proposal.dde
+Written by: James Wigglesworth
+Started: 11/13/2023
+Modified: 11/27/2023
+
+
+Proposed new functions:
+ -Dexter.j_move()
+ -Dexter.j_reset()
+ -Dexter.j_set_peak_velocity()
+ -Dexter.j_set_peak_acceleration()
+ -Dexter.j_set_peak_jerk()
+
+
+Example Dexter.j_move() calls:
+
+Dexter.j_move([0, -45, 0, 0, 0]),
+Dexter.j_move([0, -45, 0, 0, 0, 0]),
+Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}),
+Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}),
+Dexter.j_move([0, 0, 0, 0, 0, 0], {end_acceleration: [30, 0, 0, 0, 0, 0]}),
+Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}),
+
+
+
+Dexter.j_move([0, 30, 0, 0, 0, 0], {
+ end_velocity: [0, 0, 0, 0, 0, 0],
+ end_acceleration: [0, 0, 0, 0, 0, 0],
+ duration: "?", //'?' means get there as fast as possible
+ async: undefined, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value
+}),
+
+
+
+//Limits:
+var speed = [30, 30, 30, 30, 30, 30]
+var accel = [150, 150, 150, 150, 150, 150]
+var jerk = [3000, 3000, 3000, 3000, 3000, 3000]
+define_j_move_functions() //this is just to show the code for the Jobs first in this file
+
+//Job Examples:
+
+//Example 1 - simple motion
+new Job({
+ name: "j_move_example_1",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Control.loop(true, function(){
+ return [
+ Dexter.j_move([0, -45, 0, 0, 0]),
+ Dexter.j_move([0, 45, 0, 0, 0]),
+ ]
+ })
+ ]
+})
+
+Example 1 oplet strings sent per loop
+ j p 0,-162000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+ j p 0,162000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+
+
+
+//Example 2 - simple motion with duration
+new Job({
+ name: "j_move_example_2",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Dexter.j_move([0, 0, 0, 0, 0]),
+ Control.loop(true, function(){
+ return [
+ Dexter.j_move([0, -10, 0, 0, 0], {duration: 1}),
+ Dexter.j_move([0, -5, 0, 0, 0], {duration: 1}),
+ Dexter.j_move([0, 10, 0, 0, 0], {duration: 1}),
+ ]
+ })
+ ]
+})
+
+Example 2 oplet strings sent per loop
+ j p 0,-36000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 1;
+ F 50;
+ j p 0,-18000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 1;
+ F 50;
+ j p 0,36000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 1;
+ F 50;
+
+
+
+//Example 3 - Motion through a waypoint
+new Job({
+ name: "j_move_example_3",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Control.loop(true, function(){
+ return [
+ Dexter.j_move([-30, -30, 0, 0, 0, 0]),
+ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [30, 0, 0, 0, 0, 0]}),
+ Dexter.j_move([30, 0, 0, 0, 0, 0]),
+ ]
+ })
+ ]
+})
+
+Example 3 oplet strings sent per loop
+ j p -108000,-108000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+ j p 0,0,0,0,0,0;
+ j v 108000,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ j p 108000,0,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+
+
+//Example 4 - Sync vs. Async calls (This won't work until we impliment 'F' to work for j moves)
+new Job({
+ name: "j_move_example_4",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ user_data: {
+ move_count: 0,
+ },
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Control.loop(true, function(){
+ return [
+ function(){
+ this.user_data.move_count++
+ out("Synchronous j_move " + this.user_data.move_count + " starting...")
+ this.user_data.start_time = Date.now()
+ },
+ Dexter.j_move([0, -30, 0, 0, 0, 0]),
+ function(){
+ let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
+ out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration)
+ },
+
+ function(){
+ out("Asynchronous j_move " + this.user_data.move_count + " starting...")
+ this.user_data.start_time = Date.now()
+ },
+ Dexter.j_move([0, 30, 0, 0, 0, 0], {async: true}),
+ function(){
+ let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
+ out("Asynchronous j_move " + this.user_data.move_count + " completed in " + duration)
+ },
+
+ function(){
+ this.user_data.move_count++
+ out("Synchronous j_move " + this.user_data.move_count + " starting...")
+ this.user_data.start_time = Date.now()
+ },
+ Dexter.j_move([0, 0, 0, 0, 0, 0]),
+ function(){
+ let duration = Vector.round((Date.now() - this.user_data.start_time)*_ms, 5)
+ out("Synchronous j_move " + this.user_data.move_count + " completed in " + duration)
+ },
+
+ ]
+ })
+ ]
+})
+
+Example 4 oplet strings sent per loop
+ j p -108000,-108000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+ j p 0,0,0,0,0,0;
+ j v 108000,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+
+
+//Example 5 - Sync calls while moving (This won't work until we impliment sync_delay for 'F')
+new Job({
+ name: "j_move_example_5",
+ show_instructions: false,
+ when_stopped: function(){
+ return Dexter.j_reset()
+ },
+ user_data: {
+ move_count: 0,
+ },
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Control.loop(true, function(){
+ return [
+ Dexter.j_move([0, -30, 0, 0, 0, 0]),
+ Dexter.j_move([0, 0, 0, 0, 0, 0], {end_velocity: [0, 30, 0, 0, 0, 0], async: false, sync_delay: -0.1}),
+ function(){
+ this.user_data.move_count++
+ out("Midpoint " + this.user_data.move_count)
+ },
+ Dexter.j_move([0, 30, 0, 0, 0, 0]),
+ ]
+ })
+ ]
+})
+
+Example 5 oplet strings sent per loop
+ j p 0,-108000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+ j p 0,0,0,0,0,0;
+ j v 0,108000,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F -100;
+ j p 0,108000,0,0,0,0;
+ j v 0,0,0,0,0,0;
+ j a 0,0,0,0,0,0 ?;
+ F 50;
+
+
+new Job({
+ name: "stop_j_move",
+ inter_do_item_dur: 0,
+ show_instructions: false,
+ do_list: [
+ Dexter.j_reset()
+ ]
+})
+
+new Job({
+ name: "Home",
+ inter_do_item_dur: 0,
+ show_instructions: false,
+ do_list: [
+ j_set_hardware_limits(),
+ Dexter.j_set_peak_velocity(speed),
+ Dexter.j_set_peak_acceleration(accel),
+ Dexter.j_set_peak_jerk(jerk),
+
+ Dexter.j_move([0, 0, 0, 0, 0])
+ ]
+})
+*/
+ //stops Dexter from moving where ever it is and leaves it there.
+ Dexter.j_reset = function(){
+ return make_ins("j")
+ }
+
+ Dexter.j_set_peak_velocity = function(velocity = [30, 30, 30, 30, 30, 30]){
+ let cmd = []
+ for(let i = 0; i < velocity.length; i++){
+ cmd.push(make_ins("S", "JointSpeed " + (i+1) + " " + Math.round(velocity[i]/_arcsec)))
+ }
+ return cmd
+ }
+
+ Dexter.j_set_peak_acceleration = function(acceleration = [300, 300, 300, 300, 300, 300]){
+ let cmd = []
+ for(let i = 0; i < accel.length; i++){
+ cmd.push(make_ins("S", "JointAcceleration " + (i+1) + " " + Math.round(acceleration[i]/_arcsec)))
+ }
+ return cmd
+ }
+
+ Dexter.j_set_peak_jerk = function(jerk = [3000, 3000, 3000, 3000, 3000, 3000]){
+ let cmd = []
+ for(let i = 0; i < jerk.length; i++){
+ cmd.push(make_ins("S", "JointJerk " + (i+1) + " " + Math.round(jerk[i]/_arcsec)))
+ }
+ return cmd
+ }
+
+
+ //goal_position is an array of angles, its lengths should be the same as the
+ //measured angles that we compare to.
+ //Truncate rs[Dexter.J1_MEASURED_ANGLE], based on goal_position length
+ Dexter.wait_until_measured_angles = function(goal_position, tolerance = 0.05, callback){
+ return Control.loop(true, function() {
+ let cmd = []
+
+ let rs = this.robot.robot_status
+ let status_mode = rs[Dexter.STATUS_MODE]
+ let meas
+ if (status_mode === 0) {
+ meas = [
+ rs[Dexter.J1_MEASURED_ANGLE],
+ rs[Dexter.J2_MEASURED_ANGLE],
+ rs[Dexter.J3_MEASURED_ANGLE],
+ rs[Dexter.J4_MEASURED_ANGLE],
+ rs[Dexter.J5_MEASURED_ANGLE],
+ ]
+ } else if (status_mode === 5) {
+ meas = [
+ rs[Dexter.g5_J1_MEASURED_ANGLE],
+ rs[Dexter.g5_J2_MEASURED_ANGLE],
+ rs[Dexter.g5_J3_MEASURED_ANGLE],
+ rs[Dexter.g5_J4_MEASURED_ANGLE],
+ rs[Dexter.g5_J5_MEASURED_ANGLE],
+ ]
+ meas = Vector.multiply(meas, _arcsec)
+ }
+
+ let error = Vector.max(Vector.abs(Vector.subtract(meas, goal_position.slice(0, 5))))
+ cmd.push(callback)
+ if (error < tolerance) {
+ cmd.push(Control.break())
+ } else {
+ //cmd.push(Dexter.get_robot_status())
+ cmd.push(make_ins("g", 5))
+ }
+ return cmd
+ })
+ }
+
+ Dexter.j_move = function(j_angles = [], options = {
+ end_velocity: [0, 0, 0, 0, 0, 0],
+ end_acceleration: [0, 0, 0, 0, 0, 0],
+ duration: "?",
+ async: false,
+ sync_delay: 0.05,
+ peak_velocity: undefined,
+ peak_acceleration: undefined,
+ peak_jerk: undefined,
+ monitor_callback: function(){}
+ }){
+
+ let default_options = {
+ end_velocity: [0, 0, 0, 0, 0, 0],
+ end_acceleration: [0, 0, 0, 0, 0, 0],
+ duration: "?", //'?' means get there as fast as possible
+
+ async: false, //defualts true if coming to stop, false if moving through point, will always be overwritten by user value
+ sync_delay: 0.05, //time from JtQ completion. Negative value means 'time before completion'.
+
+ peak_velocity: undefined,
+ peak_acceleration: undefined,
+ peak_jerk: undefined,
+
+ monitor_callback: function(){} //This function will get called over and over waiting for movement to complete. It returns to the do_list.
+ }
+
+ if(j_angles.length === 5){
+ j_angles.push(0)
+ }else if(j_angles.length !== 6){
+ dde_error("Dexter.j_move() was passed: " + j_angles + "
It must have 5 or 6 joint angles as arguments")
+ }
+
+ let option_keys = Object.keys(options)
+ let default_option_keys = Object.keys(default_options)
+ for(let i = 0; i < option_keys.length; i++){
+ if(!default_option_keys.includes(option_keys[i])){
+ dde_error("Dexter.j_move() passed an invalid option: " + option_keys[i] + "
Valid options: " + JSON.stringify(default_option_keys))
+ }
+ default_options[option_keys[i]] = options[option_keys[i]]
+ }
+
+ /*
+ if(default_options.async === false || Vector.is_equal(default_options.end_velocity, [0, 0, 0, 0, 0, 0]) && Vector.is_equal(default_options.end_acceleration, [0, 0, 0, 0, 0, 0])){
+ default_options.async = false
+ }else{
+ default_options.async = true
+ }
+ */
+
+ if(default_options.end_velocity.length === 5){
+ default_options.end_velocity.push(0)
+ }
+
+ if(default_options.end_acceleration.length === 5){
+ default_options.end_acceleration.push(0)
+ }
+
+
+ let cmd = []
+
+ if(default_options.peak_velocity !== undefined){
+ cmd.push(Dexter.j_set_peak_velocity(default_options.peak_velocity))
+ }
+ if(default_options.peak_acceleration !== undefined){
+ cmd.push(Dexter.j_set_peak_acceleration(default_options.peak_acceleration))
+ }
+ if(default_options.peak_jerk !== undefined){
+ cmd.push(Dexter.j_set_peak_jerk(default_options.peak_jerk))
+ }
+
+ cmd.push(make_ins("j", "p", ...Vector.round(Vector.multiply(j_angles, 1/_arcsec), 0)))
+ cmd.push(make_ins("j", "v", ...Vector.round(Vector.multiply(default_options.end_velocity, 1/_arcsec), 0)))
+ cmd.push(make_ins("j", "a", ...Vector.round(Vector.multiply(default_options.end_acceleration, 1/_arcsec), 0), default_options.duration))
+
+ if(default_options.async === false){
+ //Eventually this will be the F oplet but for now:
+ cmd.push(Dexter.wait_until_measured_angles(j_angles, undefined, options.monitor_callback))
+ cmd.push(Control.wait_until(options.sync_delay))
+ //cmd.push(make_ins("F", Math.round(default_options.sync_delay/_ms))) //This may or may not become a different oplet specifically for j moves. Units could become seconds too.
+ }
+
+ return cmd
+ }
+
+function init_g5_robot_status_indexes() {
+ let keys = ["MEASURED_ANGLE", "STEP_ANGLE", "ANGLE_AT", "J_POSITION", "J_VELOCITY", "J_ACCELERATION", "J_temp_reserved_1", "J_temp_reserved_2"]
+ for (let i = 0; i < 6; i++) {
+ for (let j = 0; j < keys.length; j++) {
+ let key = "g5_J" + (i + 1) + "_" + keys[j]
+ let idx = i * keys.length + 10 + j
+ Dexter[key] = idx
+ }
+ }
+}
+init_g5_robot_status_indexes()
+
+/*
+
+//This should not be a DDE function, these should be defined correctly when a robot is first made and never changed.
+function j_set_hardware_limits(){
+ //I'll come back and make this have args for everything later
+ return [
+ "S, JointHardwareMaxSpeed, 1, 2880000;",
+ "S, JointHardwareMaxSpeed, 2, 2880000;",
+ "S, JointHardwareMaxSpeed, 3, 2880000;",
+ "S, JointHardwareMaxSpeed, 4, 4320000;",
+ "S, JointHardwareMaxSpeed, 5, 4320000;",
+ "S, JointHardwareMaxSpeed, 6, 5760000;",
+
+ "S, JointHardwareMaxAcceleration, 1, 28800000;",
+ "S, JointHardwareMaxAcceleration, 2, 28800000;",
+ "S, JointHardwareMaxAcceleration, 3, 28800000;",
+ "S, JointHardwareMaxAcceleration, 4, 43200000;",
+ "S, JointHardwareMaxAcceleration, 5, 43200000;",
+ "S, JointHardwareMaxAcceleration, 6, 57600000;",
+
+ "S, JointHardwareMaxJerk, 1, 324000000;",
+ "S, JointHardwareMaxJerk, 2, 324000000;",
+ "S, JointHardwareMaxJerk, 3, 324000000;",
+ "S, JointHardwareMaxJerk, 4, 324000000;",
+ "S, JointHardwareMaxJerk, 5, 324000000;",
+ "S, JointHardwareMaxJerk, 6, 324000000;"
+ ]
+}*/
diff --git a/core/robot.js b/core/robot.js
index 6a68fdec..574f10d5 100644
--- a/core/robot.js
+++ b/core/robot.js
@@ -113,7 +113,7 @@ var Robot = class Robot {
}
else { return false }
}
- else { return true }
+ else { return !is_digit(oplet) } //ie its not a letter or puncutation, so probably a digit was when we have variable-length instructions, If so, return false
}
else { return false }
}
diff --git a/core/storage.js b/core/storage.js
index 39198850..0f15ec25 100644
--- a/core/storage.js
+++ b/core/storage.js
@@ -397,36 +397,62 @@ function read_file_async_from_dexter_using_job(dex_instance, path, callback){
module.exports.read_file_async = read_file_async
-function choose_file(show_dialog_options={}) {
- const dialog = app.dialog;
- const paths = dialog.showOpenDialog(app.getCurrentWindow(),
- //passing this first arg of the window
- // makes the dialog "modal" ie can't do anything but choose a file or cancel
- //you can't even click outside the window ahd have it do anything.
- //This prevents certain error states you can get into with
- //the file dialog and DDE. Not *always* what you want but
- //on average, mostly what you want, particulary on WindowsOS which is worse
- //than MacOS when we DON'T pass this.
- //Note that "title" option doesn't show up on Mac.
- show_dialog_options)
- if (paths) {
- if (Array.isArray(paths) && (paths.length == 1)) {
- return convert_backslashes_to_slashes(paths[0]) }
- else {
- let slashed_paths = []
- for (let p of paths){
- slashed_paths.push(convert_backslashes_to_slashes(p))
- }
- return slashed_paths
+function choose_file_default_callback(err, paths){
+ if(err){
+ if(paths.length === 0){
+ warning("choose_file: no files chosen")
+ }
+ else {
+ warning("choose_file errored with: " + err)
}
}
- else { return null }
+ else {
+ out(paths)
+ }
+}
+//For the options areg to showOpenDialog
+// passing this first arg of the window
+// makes the dialog "modal" ie can't do anything but choose a file or cancel
+//you can't even click outside the window ahd have it do anything.
+//This prevents certain error states you can get into with
+//the file dialog and DDE. Not *always* what you want but
+//on average, mostly what you want, particulary on WindowsOS which is worse
+//than MacOS when we DON'T pass this.
+//Note that "title" option doesn't show up on Mac.
+function choose_file(show_dialog_options={},
+ callback=choose_file_default_callback) {
+ const dialog = app.dialog;
+ dialog.showOpenDialog(app.getCurrentWindow(), show_dialog_options).then(
+ function(an_obj){
+ let paths = an_obj.filePaths
+ if(an_obj.canceled) {
+ callback(true, paths)
+ }
+ else {
+ let path
+ if (Array.isArray(paths) && (paths.length == 1)) {
+ path = convert_backslashes_to_slashes(paths[0]) }
+ else {
+ let slashed_paths = []
+ for (let p of paths){
+ slashed_paths.push(convert_backslashes_to_slashes(p))
+ }
+ path = slashed_paths
+ }
+ callback(null, path)
+ }
+ },
+ function (reason){
+ callback(true, reason)
+ dde_warning("choose_file got error: " + reason)
+ }
+ )
}
module.exports.choose_file = choose_file
-function choose_folder(show_dialog_options={}) {
+function choose_folder(show_dialog_options={}, callback=choose_file_default_callback) {
let props = show_dialog_options.properties
if(!props) {
props = ["openDirectory"]
@@ -437,25 +463,59 @@ function choose_folder(show_dialog_options={}) {
}
}
show_dialog_options.properties = props
- return choose_file(show_dialog_options)
+ choose_file(show_dialog_options, callback)
}
module.exports.choose_folder = choose_folder
-function choose_file_and_get_content(show_dialog_options={}, encoding="utf8") {
- var path = choose_file(show_dialog_options)
- if (path){
- if (Array.isArray(path)) { path = path[0] }
- return read_file(path, encoding)
+function choose_file_and_get_content_default_callback(err, content){
+ if(err){
+ warning("choose_file_and_get_content got error: " + content )
+ }
+ else {
+ out("" + content.replaceAll("\n", "
")
}
}
+
+function choose_file_and_get_content(show_dialog_options={}, encoding="utf8", callback=choose_file_and_get_content_default_callback) {
+ choose_file(show_dialog_options, function(err, path){
+ if(err){
+ warning("choose_file_and_get_content got error: " + err)
+ }
+ else {
+ read_file_async(path, encoding, callback)
+ }
+ })
+}
module.exports.choose_file_and_get_content = choose_file_and_get_content
-function choose_save_file(show_dialog_options={}) { //todo document
+/*function choose_save_file(show_dialog_options={}) { //todo document
const dialog = app.dialog; //use {defaultPath: '~/foo.xml'} to set default file name
let result = dialog.showSaveDialog(app.getCurrentWindow(), show_dialog_options)
return convert_backslashes_to_slashes(result)
+}*/
+
+//very similar to choose_file BUT calls dialog.showSaveDialog
+function choose_save_file(show_dialog_options={}, callback) { //todo document
+ const dialog = app.dialog;
+ dialog.showSaveDialog(app.getCurrentWindow(), show_dialog_options).then(
+ function (an_obj) {
+ let path = an_obj.filePath //NOT filePaths
+ if (an_obj.canceled) {
+ callback(true, path)
+ }
+ else {
+ path = convert_backslashes_to_slashes(path)
+ callback(null, path)
+ }
+ },
+ function (reason) {
+ callback(true, reason)
+ dde_warning("choose_file got error: " + reason)
+ }
+ )
}
+
module.exports.choose_save_file = choose_save_file
function write_file(path, content, encoding="utf8"){
@@ -532,7 +592,7 @@ function write_file_async_default_callback(err){
dde_error("write_file_async error: " + err.message)
}
else {
- out("saved: file", undefined, true)
+ out("saved file", undefined, true)
}
}
diff --git a/doc/guide.html b/doc/guide.html
index 60732e90..f795bf86 100644
--- a/doc/guide.html
+++ b/doc/guide.html
@@ -8,8 +8,8 @@
") + "About
This is Dexter Development Environment
- version: 3.8.19
- released: Jan 11, 2024
+ version: 3.9.0
+ released: Feb 9, 2024
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,
diff --git a/doc/known_issues.html b/doc/known_issues.html
index d5e07021..cd0b059f 100644
--- a/doc/known_issues.html
+++ b/doc/known_issues.html
@@ -10,6 +10,8 @@
with this DDE release. All of them are on our "do list".
+
Init_Run()
has not been updated yet as this is tricky.
+ It is accessible from DDE menu/Insert/ezTeach, but is now broken.
- Parameter:
+ Parameters:
show_dialog_options A JS literal object of name-value pairs, all of which
are optional. See the options under "showOpenDialog" in showOpenDialog
for details.
+ callback a function of 2 argumets:
+ err: if true, there was an error in choosing or the user canceled.
+ if false or null, the user chose a file.
+ path: A string of the full path of the chosen file.
+
Example:
- choose_file({buttonLabel: "pick the best"})
Just lets you choose files.
- If the user chooses "cancel" in the dialog box, this function returns null
.
+
If the options indicate that the user can choose more than one path, and they do,
(using shift-click)
an array of paths is returned. Otherwise (the default) just one path (a string) is returned. choose_file({buttonLabel: "pick the best"},
+ function(err, path){ if(err) {warning("No file chosen")}
+ else { out("you chose: " + path) }
+ })
@@ -111,24 +118,40 @@
Does not actualy save the contents of the file. Use
write_file
for that.
- Parameter:
+ Parameterss:
show_dialog_options A JS literal object of name-value pairs, all of which
are optional. See the options under showSaveDialog in showSaveDialog
for details.
+ callback a function of 2 argumets:
+ err: if true, there was an error in choosing or the user canceled.
+ if false or null, the user chose a file.
+ path: A string of the full path of the chosen file.
+
Example:
- choose_save_file({buttonLabel: "save the best"})
-
choose_save_file({buttonLabel: "pick the best"},
+ function(err, path){ if(err) {warning("No file chosen")}
+ else { out("you chose: " + path) }
+ })
choose_folder()
Just lets you choose folders. choose_folder({buttonLabel: "pick the best"},
+ function(err, path){ if(err) {warning("No folder chosen")}
+ else { out("you chose: " + path) }
+ })
+
If the user chooses "cancel" in the dialog box, this function returns null
.
If the options indicate that the user can choose more than one path, and they do,
(using shift-click)
diff --git a/doc/release_notes.html b/doc/release_notes.html
index 092c47e6..cdcd8606 100644
--- a/doc/release_notes.html
+++ b/doc/release_notes.html
@@ -6,6 +6,40 @@
.doc_details summary { font-weight: 600; }
+choose_file
, choose_folder
, choose_save_file
, choose_file_and_get_content
,
+ and the File menu items that
+ depend on them fixed, using new asnchronous function calling
+ required by the lastest Electron release.
+ Note that if you are calling choose_file or choose_folder,
+ you will have to extend your calls to take a callback of 2 arguments,
+ an "err" and a "path".
+ The following menn items have changed:
+ open