diff --git a/core/dextersim.js b/core/dextersim.js index 773cfcc2..a8859504 100644 --- a/core/dextersim.js +++ b/core/dextersim.js @@ -146,6 +146,11 @@ DexterSim = class DexterSim{ //so just leave that as it is--apr 2019 if (this.sim_actual === true){ let rob = this.robot + if((rs_copy[Dexter.INSTRUCTION_TYPE] == "r") && + (typeof(payload_string_maybe) == "number") && + (payload_string_maybe > 0)){ + rs_copy[Dexter.ERROR_CODE] = payload_string_maybe + } setTimeout(function(){ Socket.on_receive(rs_copy, rob.name, payload_string_maybe) }, 1) @@ -422,13 +427,11 @@ DexterSim = class DexterSim{ let whole_content try { whole_content = read_file(source) }//errors if path in "source" doesn't exist catch(err){ - return 1 //return the error code + return 2 //return the error code } let start_index = hunk_index * Instruction.Dexter.read_file.payload_max_chars let end_index = start_index + Instruction.Dexter.read_file.payload_max_chars let payload_string = whole_content.substring(start_index, end_index) //ok if end_index is > whole_cotnent.length, it just gets how much it can, no error - //out("some content from " + source + " hunk: " + hunk_index + " payload: " + payload_string) - //Socket.r_payload_grab_aux(instruction_array, payload_string) return payload_string } //Dexter.write_file diff --git a/core/index.js b/core/index.js index 98d97907..817564b4 100644 --- a/core/index.js +++ b/core/index.js @@ -109,7 +109,11 @@ var Coor = require("../math/Coor.js") var Kin = require("../math/Kin.js") var Vector = require("../math/Vector.js") var Job = require("./job.js") + var {Robot, Brain, Dexter, Human, Serial} = require("./robot.js") +var {Control} = require("./instruction_control.js") +var {IO} = require("./instruction_io.js") + var {out} = require("./out.js") var calibrate_build_tables = require("../low_level_dexter/calibrate_build_tables.js") var DXF = require("../math/DXF.js") @@ -119,6 +123,8 @@ global.Dexter = Dexter global.make_ins = Dexter.make_ins global.out = out global.Robot = Robot +global.Control = Control +global.IO = IO global.Job = Job global.Vector = Vector global.Kin = Kin diff --git a/core/instruction.js b/core/instruction.js index 584d577a..21a8e4ab 100644 --- a/core/instruction.js +++ b/core/instruction.js @@ -443,8 +443,8 @@ Instruction.break = class Break extends Instruction{ //class name must be upper do_item (job_instance){ let loop_pc = Instruction.loop.pc_of_enclosing_loop(job_instance) if (loop_pc === null) { - warning("Job " + job_instance.name + ' has a Robot.break instruction at pc: ' + job_instance.program_counter + - "
but there is no Robot.loop instruction above it.") + warning("Job " + job_instance.name + ' has a Control.break instruction at pc: ' + job_instance.program_counter + + "
but there is no Control.loop instruction above it.") job_instance.set_up_next_do(1) } else { @@ -458,7 +458,7 @@ Instruction.break = class Break extends Instruction{ //class name must be upper } } toString(){ return "break" } - to_source_code(args){ return args.indent + "Robot.break()" } + to_source_code(args){ return args.indent + "Control.break()" } } Instruction.debugger = class Debugger extends Instruction{ //class name must be upper case because lower case conflicts with js debugger @@ -468,7 +468,7 @@ Instruction.debugger = class Debugger extends Instruction{ //class name must be job_instance.set_up_next_do(1, true) } toString(){ return "debugger" } - to_source_code(args){ return args.indent + "Robot.debugger()" } + to_source_code(args){ return args.indent + "Control.debugger()" } } Instruction.error = class error extends Instruction{ @@ -489,11 +489,11 @@ Instruction.error = class error extends Instruction{ args = jQuery.extend({}, arguments[0]) args.value = this.reason args.indent = "" - return this_indent + "Robot.error(" + to_source_code(args) + ")" + return this_indent + "Control.error(" + to_source_code(args) + ")" } } -//upper case G to avoid a conflict, but the user instruction is spelled Robot.get_page +//upper case G to avoid a conflict, but the user instruction is spelled Control.get_page Instruction.Get_page = class Get_page extends Instruction{ constructor (url_or_options, response_variable_name="http_response") { super() @@ -535,7 +535,7 @@ Instruction.Get_page = class Get_page extends Instruction{ else { job_instance.set_up_next_do(1)} //got the response, move to next instruction } to_source_code(args){ - return args.indent + "Robot.get_page(" + + return args.indent + "Control.get_page(" + to_source_code({value: this.url_or_options}) + ((this.response_variable_name == "http_response") ? "" : (", " + to_source_code({value: this.response_variable_name}))) + ")" @@ -566,14 +566,14 @@ Instruction.go_to = class go_to extends Instruction{ job_instance.set_up_next_do(0) } } - toString(){ return "Robot.go_to instruction_location: " + this.instruction_location } + toString(){ return "Control.go_to instruction_location: " + this.instruction_location } to_source_code(args){ let this_indent = args.indent args = jQuery.extend({}, arguments[0]) args.value = this.instruction_location args.indent = "" - return this_indent + "Robot.go_to(" + to_source_code(args) + ")" + return this_indent + "Control.go_to(" + to_source_code(args) + ")" } } @@ -634,7 +634,7 @@ Instruction.grab_robot_status = class grab_robot_status extends Instruction{ args.value = this.end_index args.indent = "" let ei_src = to_source_code(args) - return this_indent + "Robot.grab_robot_status(" + + return this_indent + "Control.grab_robot_status(" + ud_src + ", " + si_src + ", " + ei_src + ")" } } @@ -1768,9 +1768,9 @@ Instruction.if_any_errors = class if_any_errors extends Instruction{ let the_error_ins = this.instruction_if_error if (the_error_ins == null){ let message = "In job: " + job_instance.name + - ", an instruction of type: Robot.if_any_errors, " + + ", an instruction of type: Control.if_any_errors, " + "discovered that job: " + job_name + " has errored." - the_error_ins = Robot.error(message) + the_error_ins = Control.error(message) } job_instance.insert_single_instruction(the_error_ins) break; @@ -1778,7 +1778,7 @@ Instruction.if_any_errors = class if_any_errors extends Instruction{ } else { job_instance.stop_for_reason("errored", "In job: " + job_instance.name + - ", an instruction of type: Robot.if_any_errors
" + + ", an instruction of type: Control.if_any_errors
" + "was passed a job name of: " + job_name + "
that doesn't exist.") job_instance.set_up_next_do(0) return @@ -1787,7 +1787,7 @@ Instruction.if_any_errors = class if_any_errors extends Instruction{ job_instance.set_up_next_do(1) } to_source_code(args){ - return args.indent + "Robot.if_any_errors(" + + return args.indent + "Control.if_any_errors(" + to_source_code({value: this.job_names}) + ", " + to_source_code({value: this.instruction_if_error}) + ")" } @@ -1797,7 +1797,7 @@ Instruction.include_job = class include_job extends Instruction{ constructor (job_name, start_loc, end_loc) { super() if(job_name === undefined){ - dde_error("Robot.include_job was not passed a job_name which is required.") + dde_error("Control.include_job was not passed a job_name which is required.") } this.job_name = job_name //It *might* be good to permit job_name to be a job obj, but @@ -1815,7 +1815,7 @@ Instruction.include_job = class include_job extends Instruction{ if(!name_of_job_to_include.startsWith("Job.")) { name_of_job_to_include = "Job." + name_of_job_to_include } let job_to_include = value_of_path(name_of_job_to_include) if(!(job_to_include instanceof Job)) { - dde_error("Robot.include_job passed a job_name: " + this.job_name + + dde_error("Control.include_job passed a job_name: " + this.job_name + "
that is not bound to a Job, but rather: " + job_to_include) } let the_start_loc = ((this.start_loc === null) ? job_to_include.orig_args.program_counter : this.start_loc) @@ -1845,7 +1845,7 @@ Instruction.include_job = class include_job extends Instruction{ if(first_arg.startsWith("Job.")) { resolved_first_arg = value_of_path(first_arg) if(!(resolved_first_arg instanceof Job)){ - dde_error("Robot.include_job's first argument: " + first_arg + + dde_error("Control.include_job's first argument: " + first_arg + "
resolved to: " + resolved_first_arg + "
but was expected to resolve to a Job instance.") } @@ -1866,7 +1866,7 @@ Instruction.include_job = class include_job extends Instruction{ let file_src = read_file(first_arg) let result_obj = eval_js_part2(file_src, false) // warning: calling straight eval often doesn't return the value of the last expr in the src, but my eval_js_part2 usually does. //window.eval(file_src) if(result_obj.error_message){ - dde_error("Robot.include_job's first argument: " + first_arg + + dde_error("Control.include_job's first argument: " + first_arg + "
refers to an existing file but
" + "that file contains the JavaScript error of:
" + err.message) @@ -1881,7 +1881,7 @@ Instruction.include_job = class include_job extends Instruction{ if(file_src.startsWith("var ")){ let equal_sign_pos = file_src.indexOf("=") if(equal_sign_pos == -1){ - dde_error("Robot.include_job's first argument: " + first_arg + + dde_error("Control.include_job's first argument: " + first_arg + "
refers to an existing file containing variable: " + var_name + ".
" + "However, their is no equal sign after 'var'") } @@ -1892,7 +1892,7 @@ Instruction.include_job = class include_job extends Instruction{ do_list_array_to_use = var_val } else { - dde_error("Robot.include_job's first argument: " + first_arg + + dde_error("Control.include_job's first argument: " + first_arg + "
refers to an existing file containing variable: " + var_name + ".
" + "However, the value is not an array of instructions, but rather:
" + var_val) @@ -1902,7 +1902,7 @@ Instruction.include_job = class include_job extends Instruction{ } } else { - dde_error("Robot.include_job's first argument: " + first_arg + " has a dot in it
" + + dde_error("Control.include_job's first argument: " + first_arg + " has a dot in it
" + "so it is presumed to be a file path
" + "but no such file exists.") } @@ -1910,7 +1910,7 @@ Instruction.include_job = class include_job extends Instruction{ else if (window[first_arg]) { resolved_first_arg = window[first_arg] if(!Array.isArray(resolved_first_arg)) { - dde_error("Robot.include_job's first argument: " + first_arg + " is a variable
" + + dde_error("Control.include_job's first argument: " + first_arg + " is a variable
" + "but the value of the variable is not an array:
" + resolved_first_arg) } @@ -1919,20 +1919,20 @@ Instruction.include_job = class include_job extends Instruction{ } } else { - dde_error("Robot.include_job, got a first argument of: " + first_arg + + dde_error("Control.include_job, got a first argument of: " + first_arg + "
which is invalid because, although it is a string,
" + "it isn't a Job name, file name, nor variable name.") } } //end of first_arg is a string processing else { - dde_error("Robot.include_job, got a first argument of: " + first_arg + + dde_error("Control.include_job, got a first argument of: " + first_arg + "
which is invalid because its not a Job, array, or string.") } //at this point either the above code errored, or we have //resolved_first_arg set to a Job or a do_list array and //do_list_array_to_use set to an array if(Instruction.is_oplet_array(do_list_array_to_use)){ - dde_error("Robot.include_job, got a first argument of: " + first_arg + + dde_error("Control.include_job, got a first argument of: " + first_arg + "
but that resolved to an oplet array: " + do_list_array_to_use + "
which is not a valid array of instruction.
" + "If you wrap this oplet array in an outer array, it will be valid.") @@ -1954,12 +1954,12 @@ Instruction.include_job = class include_job extends Instruction{ if(the_end_loc == null) { the_end_loc = do_list_array_to_use.length } } if(!is_non_neg_integer(the_start_loc)){ - dde_error("Robot.include_job passed start_loc of: " + this.start_loc + + dde_error("Control.include_job passed start_loc of: " + this.start_loc + "
but that resolved to: " + the_start_loc + "
which is not a non-negative integer.") } else if(!is_non_neg_integer(the_end_loc)){ - dde_error("Robot.include_job passed end_loc of: " + this.end_loc + + dde_error("Control.include_job passed end_loc of: " + this.end_loc + "
but that resolved to: " + the_end_loc + "
which is not a non-negative integer.") } @@ -1972,7 +1972,7 @@ Instruction.include_job = class include_job extends Instruction{ } to_source_code(args){ - return args.indent + "Robot.include_job(" + + return args.indent + "Control.include_job(" + to_source_code({value: this.job_name}) + ")" } } @@ -1991,7 +1991,7 @@ Instruction.label = class label extends Instruction{ } toString(){ return this.name } to_source_code(args){ - return args.indent + "Robot.label(" + + return args.indent + "Control.label(" + to_source_code({value: this.name}) + ")" } } @@ -2019,7 +2019,7 @@ Instruction.loop = class Loop extends Instruction{ //If on a normal iteration with more to come, the last inst returned will be a //go_to to this loop instruction. (and that go_to might be the ONLY instruction in the returned array) //else if null is returned, we're done with this loop. - //the returned instruction array may contain a Robot.break instruction that + //the returned instruction array may contain a Control.break instruction that //ends this loop. That ending is handled in Job.prototype.do_next_item section that handles loop get_instructions_for_one_iteration(job_instance){ //strategy: compute: //1. iter_index, 2. iter_total,3. iter_val & iter_key, 4. instructions for this iteration & return them @@ -2041,7 +2041,7 @@ Instruction.loop = class Loop extends Instruction{ else if (typeof(fn_result) == "number"){ if(is_non_neg_integer(fn_result)) { this.resolved_times_to_loop = fn_result; this.iter_total = this.resolved_times_to_loop} else { - job_instance.stop_for_reason("errored", "Robot.loop passed times_to_loop that returned a number: " + fn_result + + job_instance.stop_for_reason("errored", "Control.loop passed times_to_loop that returned a number: " + fn_result + "\n but it isn't a non-negative integer.") return null } @@ -2053,13 +2053,13 @@ Instruction.loop = class Loop extends Instruction{ this.iter_total = this.resolved_times_to_loop.length } else if (typeof(fn_result) == "function") { this.resolved_times_to_loop = fn_result} //rare but possible. //leave iter_total at Infinity - else { job_instance.stop_for_reason("errored", "Robot.loop passed function for boolean_int_array_number but that function" + + else { job_instance.stop_for_reason("errored", "Control.loop passed function for boolean_int_array_number but that function" + "\n returned an invalid type: " + fn_result + "\n It must return a boolean, non-negative integer, array, or function") return null } } - else { job_instance.stop_for_reason("errored", "Robot.loop passed times_to_loop of:\n " + + else { job_instance.stop_for_reason("errored", "Control.loop passed times_to_loop of:\n " + this.times_to_loop + "\n but that is not one of the valid types of:\n boolean, non-negative integer, array, or function.") return null @@ -2074,7 +2074,7 @@ Instruction.loop = class Loop extends Instruction{ this.resolved_times_to_loop = null //ready for next start of this job return null } - else if (this.resolved_times_to_loop === true){ iter_val = true } //loop forever or until body_fn returns Robot.break instruction + else if (this.resolved_times_to_loop === true){ iter_val = true } //loop forever or until body_fn returns Control.break instruction else if(is_non_neg_integer(this.resolved_times_to_loop)){ iter_val = this.iter_index } @@ -2093,13 +2093,13 @@ Instruction.loop = class Loop extends Instruction{ } else if (fn_result === true) { iter_val = true } else { - job_instance.stop_for_reason("errored", "Robot.loop passed a function to call to determine if another iteration should occur" + + job_instance.stop_for_reason("errored", "Control.loop passed a function to call to determine if another iteration should occur" + "\n but that function returned: " + fn_result + "\n however, only true and false are valid results.") return null } } - else { shouldnt("Robot.loop has an invalid this.resolved_times_to_loop of: " + this.resolved_times_to_loop)} + else { shouldnt("Control.loop has an invalid this.resolved_times_to_loop of: " + this.resolved_times_to_loop)} if(this.iter_index >= this.iter_total) { //done looping but initialize so if the job is restrted, the loop will restart this.resolved_times_to_loop = null return null @@ -2117,13 +2117,13 @@ Instruction.loop = class Loop extends Instruction{ //below we add the go_to at the end. } //body_fn_result can legitimately be the empty array at this point. - //it might also contain a Robot.break instruction. + //it might also contain a Control.break instruction. let go_to_ins = new Instruction.go_to(job_instance.program_counter) body_fn_result.push(go_to_ins) return body_fn_result } } - //when called, pc of job_instance will (as of Jun 11 ) be to a Robot.break instruction + //when called, pc of job_instance will (as of Jun 11 ) be to a Control.break instruction //Just search backwards for the first loop instruction and return its pc. //If job_instance.program_counter happens to be pointing at a loop, //its just returned. @@ -2135,7 +2135,7 @@ Instruction.loop = class Loop extends Instruction{ return null // not good. we didn't find an enclosing loop. this will become a warning. } to_source_code(args){ - return args.indent + "Robot.loop(" + + return args.indent + "Control.loop(" + to_source_code({value: this.times_to_loop}) + ",\n" + to_source_code({value: this.body_fn}) + ")" @@ -2150,13 +2150,13 @@ Instruction.out = class Out extends Instruction{ this.temp = temp } do_item (job_instance){ - let message = "Job: " + job_instance.name + ", instruction ID: " + job_instance.program_counter + ", Instruction type: Robot.out
" + this.val + let message = "Job: " + job_instance.name + ", instruction ID: " + job_instance.program_counter + ", Instruction type: IO.out
" + this.val out(message, this.color, this.temp) job_instance.set_up_next_do(1) } - toString() { return "Robot.out of: " + this.val } + toString() { return "IO.out of: " + this.val } to_source_code(args){ - return args.indent + "Robot.out(" + + return args.indent + "IO.out(" + to_source_code({value: this.val}) + ((this.color == "black") ? "" : (", " + to_source_code({value: this.color}))) + (this.temp ? (", " + to_source_code({value: this.temp})) : "") + @@ -2235,7 +2235,7 @@ Instruction.send_to_job = class send_to_job extends Instruction{ } } to_source_code(args){ - return args.indent + "Robot.send_to_job({" + + return args.indent + "Control.send_to_job({" + ((this.do_list_item == null) ? "" : ("do_list_item: " + to_source_code({value: this.do_list_item}) + ", ")) + ((this.where_to_insert === undefined) ? "" : ("where_to_insert: " + to_source_code({value: this.where_to_insert}) + ", ")) + ((this.wait_until_done === false) ? "" : ("wait_until_done: " + to_source_code({value: this.wait_until_done}) + ", ")) + @@ -2378,12 +2378,12 @@ Instruction.sent_from_job = class sent_from_job extends Instruction{ Instruction.start_job = class start_job extends Instruction{ constructor (job_name, start_options={}, if_started="ignore", wait_until_job_done=false) { if(!["ignore", "error", "restart"].includes(if_started)){ - dde_error("Robot.start_job has invalid value for if_started of: " + + dde_error("Control.start_job has invalid value for if_started of: " + if_started + '
Valid values are: "ignore", "error", "restart"') } if(![true, false].includes(wait_until_job_done)){ - dde_error("Robot.start_job has invalid value for wait_until_job_done of: " + + dde_error("Control.start_job has invalid value for wait_until_job_done of: " + if_started + '
Valid values are: true and false') } @@ -2412,17 +2412,17 @@ Instruction.start_job = class start_job extends Instruction{ let jobs_in_file = Job.instances_in_file(this.job_name) if(jobs_in_file.length > 0) { this.job_to_start = jobs_in_file[0] } else { - dde_error("Robot.start_job has a job_name that's a path to an existing file: " + this.job_name + "
" + + dde_error("Control.start_job has a job_name that's a path to an existing file: " + this.job_name + "
" + "but that file doesn't define any jobs.") } } else { - dde_error("Robot.start_job has a job_name of: " + this.job_name + + dde_error("Control.start_job has a job_name of: " + this.job_name + "
but it doesn't resolve to a Job or a file containing one.") } } if(!(this.job_to_start instanceof Job)){ - job_instance.stop_for_reason("errored", "Robot.start_job attempted to start non-existent Job." + this.job_name) + job_instance.stop_for_reason("errored", "Control.start_job attempted to start non-existent Job." + this.job_name) job_instance.set_up_next_do(0) } } @@ -2446,14 +2446,14 @@ Instruction.start_job = class start_job extends Instruction{ return } else if(["starting", "running"].includes(stat)) { - job_instance.wait_reason = "Robot.start_job waiting at instruction " + + job_instance.wait_reason = "Control.start_job waiting at instruction " + job_instance.program_counter + " for " + this.job_to_start.name + " to complete." job_instance.set_status_code("waiting") job_instance.set_up_next_do(0) return } else if(stat == "waiting") { - job_instance.wait_reason = "Robot.start_job waiting at instruction " + + job_instance.wait_reason = "Control.start_job waiting at instruction " + job_instance.program_counter + " for " + this.job_to_start.name + " to complete,\n" + "but its now waiting for: " + this.job_to_start.wait_reason job_instance.set_status_code("waiting") @@ -2462,7 +2462,7 @@ Instruction.start_job = class start_job extends Instruction{ } else if (stat == "suspended") { this.job_to_start.unsuspend() - job_instance.wait_reason = "Robot.start_job waiting at instruction " + + job_instance.wait_reason = "Control.start_job waiting at instruction " + job_instance.program_counter + " for " + this.job_to_start.name + " to complete." job_instance.set_status_code("waiting") job_instance.set_up_next_do(0) @@ -2511,7 +2511,7 @@ Instruction.start_job = class start_job extends Instruction{ } else { //if_started is tested for validity in the constructor, but just in case... shouldnt("Job." + job_instance.name + - " has a Robot.start_job instruction with an invalid " + + " has a Control.start_job instruction with an invalid " + "
if_started value of: " + this.if_started) } } @@ -2520,7 +2520,7 @@ Instruction.start_job = class start_job extends Instruction{ job_instance.set_up_next_do(1) } else { - shouldnt("Robot.start_job got a status_code from Job." + + shouldnt("Control.start_job got a status_code from Job." + this.job_to_start.name + " that it doesn't understand.") } } @@ -2528,7 +2528,7 @@ Instruction.start_job = class start_job extends Instruction{ return "start_job: " + this.job_name } to_source_code(args){ - return args.indent + "Robot.start_job(" + + return args.indent + "Control.start_job(" + to_source_code({value: this.job_name}) + (similar(this.start_options, {}) ? "" : (", " + to_source_code({value: this.start_options}))) + ((this.if_started == "ignore") ? "" : (", " + to_source_code({value: this.if_started}))) + @@ -2549,7 +2549,7 @@ Instruction.stop_job = class stop_job extends Instruction{ if (!job_to_stop) { job_to_stop = job_instance } job_to_stop.ending_program_counter = this.instruction_location if (!this.stop_reason){ - this.stop_reason = "Stopped by Job." + job_instance.name + " instruction: Robot.stop_job." + this.stop_reason = "Stopped by Job." + job_instance.name + " instruction: Control.stop_job." } job_to_stop.stop_for_reason("completed", this.stop_reason, this.perform_when_stopped) //this is not an error or interrupted, its a normal stoppage of the job. @@ -2572,7 +2572,7 @@ Instruction.stop_job = class stop_job extends Instruction{ props_args.value = this.perform_when_stopped let pws_src = to_source_code(props_args) let result = indent + - "Robot.stop_job(" + + "Control.stop_job(" + loc_src + ", " + sr_src + ", " + pws_src + @@ -2604,7 +2604,7 @@ Instruction.suspend = class suspend extends Instruction{ } } to_source_code(args){ - return args.indent + "Robot.suspend(" + + return args.indent + "Control.suspend(" + to_source_code({value: this.job_name}) + ((this.reason == "") ? "" : (", " + to_source_code({value: this.reason}))) + ")" @@ -2636,7 +2636,7 @@ Instruction.unsuspend = class unsuspend extends Instruction{ } to_source_code(args){ - return args.indent + "Robot.unsuspend(" + + return args.indent + "Control.unsuspend(" + to_source_code({value: this.job_name}) + ")" } @@ -2711,7 +2711,7 @@ Instruction.sync_point = class sync_point extends Instruction{ } } to_source_code(args){ - return args.indent + "Robot.sync_point(" + + return args.indent + "Control.sync_point(" + to_source_code({value: this.name}) + ", " + to_source_code({value: this.job_names}) + ")" @@ -2730,7 +2730,7 @@ Instruction.wait_until = class wait_until extends Instruction{ else if (Array.isArray(this.fn_date_dur) || (typeof(this.fn_date_dur) == "object")){ if(!Job.instruction_location_to_job(this.fn_date_dur, false)){ - warning("Robot.wait_until passed an array or literal object
" + + warning("Control.wait_until passed an array or literal object
" + "for an instruction location but
" + "it does not contain a job.
" + "That implies this job will wait for itself, and thus forever.
" + @@ -2739,7 +2739,7 @@ Instruction.wait_until = class wait_until extends Instruction{ } else if (fn_data_dur instanceof Job) {} else { - dde_error("Robot.wait_until instruction passed: " + this.fn_date_dur + + dde_error("Control.wait_until instruction passed: " + this.fn_date_dur + '
which is not a number, date, function,
' + '"new_instruction" or instruction location array.') } @@ -2787,7 +2787,7 @@ Instruction.wait_until = class wait_until extends Instruction{ //so that we can keep the tcp connection alive, send a virtual heartbeat let new_wait_dur_in_sec = this.fn_date_dur - (dur_from_start_in_ms / 1000) let new_instructions = [make_ins("g"), //just a do nothing to get a round trip to Dexter. - Robot.wait_until(new_wait_dur_in_sec)] //create new wait_until to wait for the remaining time + Control.wait_until(new_wait_dur_in_sec)] //create new wait_until to wait for the remaining time job_instance.insert_instructions(new_instructions) //job_instance.added_items_count[job_instance.program_counter] += 2 this is done automatically by insert_instructions this.start_time_in_ms = null //essential for the 2nd thru nth call to start() for this job. @@ -2865,7 +2865,7 @@ Instruction.wait_until = class wait_until extends Instruction{ var loc_pc = loc_job_instance.instruction_location_to_id(this.fn_date_dur) if(loc_pc > loc_job_instance.program_counter){ //wait until loc_job_instance advances if(loc_job_instance.stop_reason){ - warning("Robot.wait_until is waiting for job: " + loc_job_instance.name + + warning("Control.wait_until is waiting for job: " + loc_job_instance.name + "
but that job is stopped, so it will probably wait forever.") } job_instance.wait_reason = "a wait_until instruction_location is reached." @@ -2887,7 +2887,7 @@ Instruction.wait_until = class wait_until extends Instruction{ } } to_source_code(args){ - return args.indent + "Robot.wait_until(" + + return args.indent + "Control.wait_until(" + to_source_code({value: this.fn_date_dur, function_names: true}) + ")" } diff --git a/core/instruction_control.js b/core/instruction_control.js new file mode 100644 index 00000000..24b45c01 --- /dev/null +++ b/core/instruction_control.js @@ -0,0 +1,24 @@ +var {Robot} = require('./robot.js') + +class Control{} + +Control.break = Robot.break +Control.go_to = Robot.go_to +Control.loop = Robot.loop +Control.label = Robot.label +Control.suspend = Robot.suspend +Control.unsuspend = Robot.unsuspend +Control.sync_point = Robot.sync_point +Control.wait_until = Robot.wait_until + +Control.include_job = Robot.include_job +Control.send_to_job = Robot.send_to_job +Control.sent_from_job = Robot.sent_from_job +Control.start_job = Robot.start_job +Control.stop_job = Robot.stop_job + +Control.debugger = Robot.debugger +Control.error = Robot.error +Control.if_any_errors = Robot.if_any_errors + +module.exports.Control = Control \ No newline at end of file diff --git a/core/instruction_dexter.js b/core/instruction_dexter.js index b4d755fa..ecf28a29 100644 --- a/core/instruction_dexter.js +++ b/core/instruction_dexter.js @@ -689,7 +689,7 @@ Instruction.Dexter.read_file = class read_file extends Instruction.Dexter{ if(!this.robot) { this.set_instruction_robot_from_job(job_instance) } if (this.first_do_item_call) { const sim_actual = Robot.get_simulate_actual(this.robot.simulate) - //have to check for dexter_file_systems or else the 2nd time I rn the job, it will + //have to check for dexter_file_systems or else the 2nd time I run the job, it will //have a double length path with 2 dexter_file_systems parts if (!this.source.startsWith("/") && (sim_actual === true) && !this.source.startsWith("dexter_file_systems")) { this.fuller_source = "dexter_file_systems/" + this.robot.name + "/" + this.source @@ -703,23 +703,23 @@ Instruction.Dexter.read_file = class read_file extends Instruction.Dexter{ //the below can never happen //if (this.is_done) { // this.processing_r_instruction = false - // return Robot.break() + // return Control.break() //} let read_file_instance = this let robot = this.robot //closed over - job_instance.insert_single_instruction(Robot.loop(true, function(content_hunk_index){ + job_instance.insert_single_instruction(Control.loop(true, function(content_hunk_index){ let job_instance = this if (read_file_instance.is_done) { //init this inst just in case it gets used again read_file_instance.is_done = false read_file_instance.first_do_item_call = true read_file_instance.processing_r_instruction = false - return Robot.break() + return Control.break() } else { read_file_instance.processing_r_instruction = true return [make_ins("r", content_hunk_index, read_file_instance.fuller_source, robot), - Robot.wait_until(function(){ + Control.wait_until(function(){ return !read_file_instance.processing_r_instruction }) ] @@ -744,6 +744,7 @@ Instruction.Dexter.read_file = class read_file extends Instruction.Dexter{ } //called from socket.js + //payload_string_maybe is a string or an error code (an int > 0) static got_content_hunk(job_id, ins_id, payload_string_maybe){ let job_instance = Job.job_id_to_job_instance(job_id) if (job_instance == null){ @@ -764,9 +765,13 @@ Instruction.Dexter.read_file = class read_file extends Instruction.Dexter{ } } - //used by Dexter.write_file too + //used by Dexter.write_file to prepare path for passing it to make_ins("W" ...) + //because the path used for write_file defaults to "srv/samba/share/dde_apps", + //whereas the path for make_ins("W" ...) defaults to srv/samba/share + //see Dexter.srv_samba_share_default_to_absolute_path to do the opposite static add_default_file_prefix_maybe(path){ - if(path.startsWith("/")) { return path } + if (path.startsWith("/")) { return path } + else if (path.startsWith("#")) { return path } else if (path.startsWith("./")) { return "dde_apps/" + path.substring(2) } else if (path.startsWith("../")) { return path.substring(3) } //will go to dexrun's default foler, ie /srv/samba/share/ else { return "dde_apps/" + path } diff --git a/core/instruction_io.js b/core/instruction_io.js new file mode 100644 index 00000000..8089e3c6 --- /dev/null +++ b/core/instruction_io.js @@ -0,0 +1,16 @@ +var {Robot} = require('./robot.js') + +class IO{} + +IO.get_page = Robot.get_page +IO.grab_robot_status = Robot.grab_robot_status +IO.out = Robot.out +IO.show_picture = Robot.show_picture +IO.show_video = Robot.show_video +IO.take_picture = Robot.take_picture +//read_file and write_file are Dexter-specific instructions only, +//so they are under Dexter.read_file and Dexter.write_file + +module.exports.IO = IO + + diff --git a/core/job.js b/core/job.js index 8871b7f8..22b6fa45 100644 --- a/core/job.js +++ b/core/job.js @@ -253,10 +253,10 @@ class Job{ inter_do_item_dur: 0.005, //we don't need to have fast communication with Dexter. Minimize traffic do_list:[ Dexter.write_file("job/run/" + dde_monitor_job_instance.name + ".dde", job_src), - Robot.loop(true, + Control.loop(true, function(){ if(this.user_data.dexter_log !== undefined) { //got a dexter log meaning the monitored job is over. - return Robot.break() + return Control.break() } else if ((this.user_data.stop_job_running_on_dexter) && (!this.user_data.already_handled_stop_job)) { //set by clicking the job button @@ -1658,7 +1658,7 @@ Job.prototype.handle_start_object = function(cur_do_item){ } } else if(cur_do_item.dur) { - this.insert_single_instruction(Robot.wait_until(cur_do_item.dur)) + this.insert_single_instruction(Control.wait_until(cur_do_item.dur)) } if (!start_args) { cur_do_item.start.apply(the_inst_this) } else if (Array.isArray(start_args)) { cur_do_item.start.apply(the_inst_this, start_args) } @@ -2261,7 +2261,7 @@ Job.prototype.increment_added_items_count_for_parent_instruction_of = function(i (type_of(par_loc_index) == "number") && (par_loc_index < this.program_counter)) { //backwards goto in same job let loop_inst_maybe = this.do_list[par_loc_index] - if(loop_inst_maybe instanceof Robot.loop){ //shoot, we can't make the inserted instruction a sub_object of a loop's go_to + if(loop_inst_maybe instanceof Control.loop){ //shoot, we can't make the inserted instruction a sub_object of a loop's go_to //so we've got to climb up the tree and increment the next instr that has a positive added_items_count //but that aic must "contain" the instr_id of the added instruction for(let maybe_par_id = par_loc_index - 1; maybe_par_id >= 0; maybe_par_id--){ diff --git a/core/out.js b/core/out.js index f9626490..05ffdbfb 100644 --- a/core/out.js +++ b/core/out.js @@ -108,6 +108,7 @@ function out_eval_result(text, color="#000000", src){ src = replace_substrings(src, "'", "'") src_formatted = "  " + src_formatted + src_formatted_suffix + " " } + //if (src_formatted == "") { console.log("_____out_eval_result passed src: " + src + " with empty string for src_formatted and text: " + text)} text = "
Result of evaling " + src_formatted + "" + text + "
" append_to_output(text) } diff --git a/core/robot.js b/core/robot.js index db9307c9..81dc2e7b 100644 --- a/core/robot.js +++ b/core/robot.js @@ -114,10 +114,10 @@ var Robot = class Robot { } //Control Instructions - static break(){ //stop a Robot.loop + static break(){ //stop a Control.loop return new Instruction.break() } - static debugger(){ //stop a Robot.loop + static debugger(){ //stop a Control.loop return new Instruction.debugger() } static error(reason){ //declare that an error happened. This will cause the job to stop. @@ -231,7 +231,7 @@ var Robot = class Robot { //default to undefined. static get_page(url_or_options, response_variable_name="http_response"){ if(url_or_options === undefined){ - dde_error("Robot.get_page called with no url_or_options argument
" + + dde_error("Control.get_page called with no url_or_options argument
" + "which is typically the string of a url.") } return new Instruction.Get_page(url_or_options, response_variable_name) @@ -1071,16 +1071,16 @@ Dexter = class Dexter extends Robot { } else if ((this_robot.simulate === false) || (this_robot.simulate === "both")){ this_job.stop_for_reason("errored", "The job: " + this_job.name + " is using robot: " + this_robot.name + - "
\nbut could not connect with a Dexter robot at: " + + "
but could not connect with a Dexter robot at: " + this_robot.ip_address + " port: " + this_robot.port + - "
\nYou can change this robot to simulate=true and run it.") + "
You can change this robot to simulate=true and run it.") } else if (this_robot.simulate === null){ if ((sim_actual === false) || (sim_actual === "both")){ this_job.stop_for_reason("errored", "The job: " + this_job.name + " is using robot: " + this_robot.name + - '
\nwith the Jobs menu "Simulate?" item being: ' + sim_actual + - "
\nbut could not connect with Dexter." + - "
\nYou can use the simulator by switching the menu item to 'true'. ") + '
with the Jobs menu "Simulate?" item being: ' + sim_actual + + "
but could not connect with Dexter." + + "
You can use the simulator by switching the menu item to 'true'. ") out("Could not connect to Dexter.", "red") } else { @@ -1965,8 +1965,9 @@ Dexter.write_file = function(file_name=null, content=""){ return instrs } -//depricated. Note reversed args from Dexter.write_file +//depricated. Note reversed args from Dexter.write_file and default path adjustment Dexter.write_to_robot = function(content="", file_name=null){ + file_name = Dexter.srv_samba_share_default_to_absolute_path(file_name) return Dexter.write_file(file_name, content) } @@ -2009,12 +2010,27 @@ new Job({name: "my_job", do_list: [out(Dexter.write_file(data, "/srv/samba/share/test.txt"))]}) */ -Dexter.read_file = function (source, destination){ +Dexter.read_file = function(source, destination="read_file_content"){ return new Instruction.Dexter.read_file(source, destination) } -Dexter.read_from_robot = Dexter.read_file //depricated +//examples pf path input: +// ./foo.txt => /srv/samba/share/foo.txt +// ../foo.txt => /srv/samba/foo.txt +// foo.txt => /srv/samba/share/foo.txt +Dexter.srv_samba_share_default_to_absolute_path = function(path){ + if (path.startsWith("/")) { return path } + else if (path.startsWith("#")) { return path } + else if (path.startsWith("./")) { return "/srv/samba/share/" + path.substring(2) } + else if (path.startsWith("../")) { return "/srv/samba/" + path.substring(3) } + else { return "/srv/samba/share/" + path } +} + +Dexter.read_from_robot = function (source, destination="read_file_content"){ //depricated. simlar to read_file but differs in that srv_sama_share is the default folder + source = Dexter.srv_samba_share_default_to_absolute_path(source) + return Dexter.read_file(source, destination) +} -Dexter.prototype.read_file = function (source, destination){ +Dexter.prototype.read_file = function (source, destination="read_file_content"){ return new Instruction.Dexter.read_file(source, destination, this) } diff --git a/core/socket.js b/core/socket.js index 86f5937c..0b7b8bb8 100755 --- a/core/socket.js +++ b/core/socket.js @@ -110,7 +110,11 @@ var Socket = class Socket{ const name = instruction_array[Instruction.INSTRUCTION_ARG0] const args = instruction_array.slice(Instruction.INSTRUCTION_ARG1, instruction_array.length) const first_arg = args[0] - if(["MaxSpeed", "StartSpeed", "Acceleration"].includes(name)){ + //first convert degress to arcseconds + if(["MaxSpeed", "StartSpeed", "Acceleration", + "AngularSpeed", "AngularSpeedStartAndEnd", "AngularAcceleration", + "CartesianPivotSpeed", "CartesianPivotSpeedStart", "CartesianPivotSpeedEnd", + "CartesianPivotAcceleration", "CartesianPivotStepSize" ].includes(name)){ let instruction_array_copy = instruction_array.slice() instruction_array_copy[Instruction.INSTRUCTION_ARG1] = Math.round(first_arg * _nbits_cf) return instruction_array_copy @@ -120,6 +124,15 @@ var Socket = class Socket{ instruction_array_copy[Instruction.INSTRUCTION_ARG1] = Math.round(first_arg * 3600) //deg to arcseconds return instruction_array_copy } + else if (["CommandedAngles", "RawEncoderErrorLimits", "RawVelocityLimits"].includes(name)){ + let instruction_array_copy = instruction_array.slice() + for(let i = Instruction.INSTRUCTION_ARG1; i < instruction_array.length; i++){ + let orig_arg = instruction_array_copy[1] + instruction_array_copy[i] = Math.round(orig_arg * 3600) + } + return instruction_array_copy + } + //dynamixel conversion else if (name == "EERoll"){ //J6 no actual conversion here, but this is a convenient place //to put the setting of robot.angles and is also the same fn where we convert // the degrees to dynamixel units of 0.20 degrees @@ -131,15 +144,22 @@ var Socket = class Socket{ rob.angles[6] = first_arg * Socket.DEGREES_PER_DYNAMIXEL_UNIT return instruction_array } - else if (name == "CommandedAngles"){ + //convert meters to microns + else if ((name.length == 5) && name.startsWith("Link")){ let instruction_array_copy = instruction_array.slice() - for(let i = Instruction.INSTRUCTION_ARG1; i < instruction_array.length; i++){ + let new_val = Math.round(first_arg / _um) //convert from meters to microns + instruction_array_copy[Instruction.INSTRUCTION_ARG1] = new_val + } + else if ("LinkLengths" == "name"){ + let instruction_array_copy = instruction_array.slice() + for(let i = Instruction.INSTRUCTION_ARG1; i < instruction_array.length; i++){ let orig_arg = instruction_array_copy[1] - instruction_array_copy[i] = Math.round(orig_arg * 3600) + instruction_array_copy[i] = Math.round(orig_arg / _um) } return instruction_array_copy } - else if ((name.length == 5) && name.startsWith("Link")){ + else if (["CartesianSpeed", "CartesianSpeedStart", "CartesianSpeedEnd", "CartesianAcceleration", + "CartesianStepSize", ].includes(name)){ let instruction_array_copy = instruction_array.slice() let new_val = Math.round(first_arg / _um) //convert from meters to microns instruction_array_copy[Instruction.INSTRUCTION_ARG1] = new_val @@ -247,9 +267,10 @@ var Socket = class Socket{ //rob.robot_done_with_instruction and the rs_status from Dexter, not the simulated one. }*/ + //called both from Dexter returning, and from Sim. //data should be a Buffer object. https://nodejs.org/api/buffer.html#buffer_buffer //payload_string_maybe is undefined when called from the robot, - //and if called from sim is either a string (everything ok) + //and if called from sim and we have an "r" oplet, it is either a string (everything ok) //or an integer (1) when sim get file-not-found. // static on_receive(data, robot_name, payload_string_maybe){ @@ -274,7 +295,7 @@ var Socket = class Socket{ robot_status[Dexter.INSTRUCTION_TYPE] = oplet if(oplet == "r"){ //Dexter.read_file - if(typeof(payload_string_maybe) == "number") { //should be 1 + if(typeof(payload_string_maybe) == "number") { //only can hit im sim.// should be 2 if it hits robot_status[Dexter.ERROR_CODE] = 0 //even though we got an error from file_not_found, //don't set the error in the robot status. Just let that error //be used in r_payload_grab_aux which passes it to got_content_hunk @@ -282,37 +303,38 @@ var Socket = class Socket{ // read_file_instance.is_done = true //so the loop in read_file_instance terminates normally. } + else if ((payload_string_maybe === undefined) && //real. not simulated + (robot_status[Dexter.ERROR_CODE] > 0)) { //got an error, probably file not found + payload_string_maybe = robot_status[Dexter.ERROR_CODE] + robot_status[Dexter.ERROR_CODE] = 0 + } + //now robot_status does NOT have an error code, but if there is an error, + //payload_string_maybe is an int > 0 + //but if no error, payload_string_maybe is a string Socket.r_payload_grab(data, robot_status, payload_string_maybe) } else { Socket.convert_robot_status_to_degrees(robot_status) } - //let job_id = robot_status[Dexter.INSTRUCTION_JOB_ID] - //let job_instance = Job.id_to_job(job_id) - //let robot_name = job_instance.robot.name 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 } static r_payload_grab(data, robot_status, payload_string_maybe) { - let payload_string - if(payload_string_maybe === undefined) { + if(payload_string_maybe === undefined) { //only in real, not in sim let payload_length = robot_status[Socket.PAYLOAD_LENGTH] let data_start = Socket.PAYLOAD_START let data_end = data_start + payload_length - payload_string = (data.slice(data_start, data_end).toString()) + payload_string_maybe = (data.slice(data_start, data_end).toString()) } else if (payload_string_maybe instanceof Buffer) { //beware, sometimes payload_string_maybe is a buffer. This converts it to a string. - payload_string = payload_string_maybe.toString() - } - else { - payload_string = payload_string_maybe //normally a string, but is an integer if file not found + payload_string_maybe = payload_string_maybe.toString() } - Socket.r_payload_grab_aux(robot_status, payload_string) //payload_string still might be an integer error code, ie 1 when file not found + //else { payload_string_maybe is normally a string, but could be an integer of > 0 if there's an error } + Socket.r_payload_grab_aux(robot_status, payload_string_maybe) //payload_string still might be an integer error code, ie 1 when file not found } - //called by both Socket.r_payload_grab AND DexterSim.process_next_instruction_r //payload_string_maybe could be a string or an integer error code like 1 when no file found static r_payload_grab_aux(robot_status, payload_string_maybe){ let job_id = robot_status[Dexter.JOB_ID] diff --git a/core/storage.js b/core/storage.js index 77435048..7e142c46 100644 --- a/core/storage.js +++ b/core/storage.js @@ -80,7 +80,13 @@ function persistent_save(){ const path = add_default_file_prefix_maybe("dde_persistent.json") var content = JSON.stringify(persistent_values) content = replace_substrings(content, ",", ",\n") //easier to read & edit - content = "//Upon DDE launch, this file is loaded before dde_apps/dde_init.js\n//Use persistent_get(key) and persistent_set(key, new_value)\n//to access each of the below variables.\n\n" + content + content = "//This file content must live in Documents/dde_apps/dde_persistent.json\n" + + "//Upon DDE launch, this file is loaded before Documents/dde_apps/dde_init.js\n" + + "//Because this file is automatically saved while running DDE, only edit it with DDE closed.\n" + + "//It must be syntactically perfect, so edit it with care.\n" + + "//Within DDE, use persistent_get(key) and persistent_set(key, new_value)\n" + + "//to access each of the below variables.\n\n" + + content write_file(path, content) } module.exports.persistent_save = persistent_save @@ -95,12 +101,14 @@ function persistent_load(){ persistent_values = JSON.parse(content) //just in case files got saved out with backslashes, change to only slashes. let files = persistent_values.files_menu_paths - let slashified_files = [] - for(let file of files){ - file = convert_backslashes_to_slashes(file) - slashified_files.push(file) + if(files){ + let slashified_files = [] + for(let file of files){ + file = convert_backslashes_to_slashes(file) + slashified_files.push(file) + } + persistent_values.files_menu_paths = slashified_files } - persistent_values.files_menu_paths = slashified_files } } @@ -177,6 +185,7 @@ function dde_init_dot_js_initialize() { } else { //the folder exists, but no dde_init.js file const initial_dde_init_content = + '//This file content must live in Documents/dde_apps/dde_init.js\n' + '//This file is loaded when you launch DDE.\n' + '//Add whatever JavaScript you like to the end.\n' + '\n' + @@ -418,11 +427,11 @@ module.exports.file_exists = file_exists //only works for dde computer, not dexter computer paths. function make_folder(path){ - path = make_full_path(path) - let path_array = path.split("/") + path = make_full_path(path) //now parh is os_specific + let path_array = path.split(folder_separator()) let path_being_built = "" for(let path_part of path_array){ - path_being_built += ("/" + path_part) + path_being_built += (folder_separator() + path_part) let path_to_use = adjust_path_to_os(path_being_built) if(!file_exists(path_to_use)){ diff --git a/core/to_source_code.js b/core/to_source_code.js index 7c5a5a2f..b9085a2d 100644 --- a/core/to_source_code.js +++ b/core/to_source_code.js @@ -86,7 +86,8 @@ function to_source_code_array(args){ function to_source_code_instruction_array(args){ let inst_array = args.value - let result = args.indent + "make_ins(" + let the_indent = ((args.indent === undefined) ? "" : args.indent) + let result = the_indent + "make_ins(" let prop_args = Object.assign({}, args) //jQuery.extend({}, args) prop_args.indent = "" for(let prop_index in inst_array) { diff --git a/core/utils.js b/core/utils.js index 268d26e8..c8cfc825 100644 --- a/core/utils.js +++ b/core/utils.js @@ -1214,7 +1214,7 @@ module.exports.trim_string_quotes = trim_string_quotes //returns a string that starts with the first char of src //by trimming whitespace and comments from the front of src -//used by Robot.include_job +//used by Control.include_job function trim_comments_from_front(src){ src = src.trimLeft() if(src.startsWith("//")) { @@ -1286,6 +1286,14 @@ function format_number(num, digits_before_point=6, digits_after_point=3, allow_f } module.exports.format_number = format_number +// ordinal_string(0) => "0th" ordinal_string(1) => "1st" +// ordinal_string(11) => "11th" ordinal_string(21) => "21st" +function ordinal_string(n){ + let suffix = ["st","nd","rd"][((n+90)%100-10)%10-1]||"th" + return n + suffix +} +module.exports.ordinal_string = ordinal_string + //used by users in calling DXF.init_drawing for its dxf_filepath arg function text_to_lines(text) { return txt.text_to_lines(text) } module.exports.text_to_lines = text_to_lines diff --git a/de/de_testsuite.js b/de/de_testsuite.js index c54311e9..c250628c 100644 --- a/de/de_testsuite.js +++ b/de/de_testsuite.js @@ -1,6 +1,6 @@ new TestSuite("DE comparison", - ['DE.de_to_js("4 lessasdfsadf with 67.")', '"(4 < 67)"'], + ['DE.de_to_js("4 less with 67.")', '"(4 < 67)"'], ['DE.de_to_js("4 more than with 67.")', '"(4 > 67)"'], ['DE.de_to_js("44 equals with 67.")', '"(44 == 67)"'], ['DE.de_to_js("44 not equals with 67.")', '"(44 != 67)"'], @@ -51,7 +51,10 @@ new TestSuite("DE call", ['DE.de_to_js("launch with .")', '"launch()"'], ['DE.de_to_js("launch with.")', '"launch()"'], ['DE.de_to_js("launch with .")', '"launch()"'], - ['DE.de_to_js("launch with . ")', '"launch()"'] + ['DE.de_to_js("launch with . ")', '"launch()"'], + ['DE.de_to_js("man/Joe launch with 123, 456.")', "man.Joe.launch(123, 456)"] + + ) new TestSuite("DE comment", @@ -101,7 +104,9 @@ new Testsuite("fn def", ['DE.de_to_js("to double with num default 2. do num plus with num.!")', '"function double(num=2){(num + num)}"'], ['DE.de_to_js("to double with num of 2. do num plus with num.!")', - '"function double({num=2}={}){(num + num)}"'] + '"function double({num=2}={}){(num + num)}"'], + ['DE.de_to_js("to unnamed with num of 2. do num plus with num.!"', //not implemented + '"function({num=2}={}){(num + num)}"'] ) new TestSuite("keyword_exprs_block", @@ -109,6 +114,7 @@ new TestSuite("keyword_exprs_block", ['DE.de_to_js("otherwise do 3 plus with 4.!")', '"else{(3 + 4)}"'], ['DE.de_to_js("finally do 3 plus with 4.!")', '"finally{(3 + 4)}"'], ['DE.de_to_js("if error err do out with 234. 22 plus with 44.!")', '"catch(err){out(234)\\n(22 + 44)}"'] + ) new TestSuite("if_but_if", @@ -123,6 +129,10 @@ new TestSuite("if_but_if", ['DE.de_to_js("if true do!")', '"if(true){}"'] ) + + + + DE.eval("loop 5 do loop_result push with loop_value.!") DE.de_to_js("loop 5 do out with loop_value.!") diff --git a/de/de_testsuite_backup.js b/de/de_testsuite_backup.js index 5672e96d..17a31fc8 100644 --- a/de/de_testsuite_backup.js +++ b/de/de_testsuite_backup.js @@ -49,7 +49,8 @@ new TestSuite("DE call", ['DE.de_to_js("launch with .")', '"launch()"'], ['DE.de_to_js("launch with.")', '"launch()"'], ['DE.de_to_js("launch with .")', '"launch()"'], - ['DE.de_to_js("launch with . ")', '"launch()"'] + ['DE.de_to_js("launch with . ")', '"launch()"'], + ['DE.de_to_js("man/Joe launch with 123, 456.")', "man.Joe.launch(123, 456)"] ) new TestSuite("DE comment", diff --git a/doc/eval.html b/doc/eval.html index a614d726..8eebc294 100644 --- a/doc/eval.html +++ b/doc/eval.html @@ -8,7 +8,7 @@ samp { background-color: #cefff3; } .new_code { background-color: #ffd9b4; } -

How to Think Like a Computer

+

How to Think Like a Computer

Fry Sep 13, 2017
Note: most of this content is available in video format at How To Think Like A Computer
diff --git a/doc/guide.html b/doc/guide.html index 0ce05d39..f95cf4cc 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -330,12 +330,10 @@
Verify Joint Wiring To verify that all 5 joints are wired correctly: -
  1. Verify that the Jumpers on processor board are configured like in this picture.
    - processor board jumpers picture.
    - This is a picture of the lower half of Dexter's processor board. - The 3 jumpers that must be in the right position are the horizontal - black rectangles on the lower left part of the board. -
  2. +
    1. Verify that the jumpers on Dexter's processor board are configured correctly. + See Troubleshooting + for details. +
    2. Choose DDE's Jobs menu/Run Instruction/Show Dialog..."
    3. for J1 through J5, make each Joint's 30 degrees with the others at 0, and click on move_all_joints and observe that that joint should move by 30 degrees. @@ -358,7 +356,7 @@ On Macs, click on the "Radio waves" icon in the upper right of the screen.
      Choose "Turn WiFi Off".

      - + Dexter Networking details.
      Using DDE
      In the Misc pane header simulate radion buttons, choose "real". @@ -724,7 +722,7 @@
      Step 3
      now in the Output pane header to run it.
    4. Debug entire Job by:
      • Looking at its output in the Output pane from - error messages and Robot.out instructions.
      • + error messages and IO.out instructions.
      • Looking at the Simulate Dexter pane (select from the Misc pane header menu).
      • Inspect the Job's data structure @@ -760,7 +758,7 @@
        Step 3
        Checking the pause checkbox will cause running jobs to pause when they have completed their current instruction. Jobs started when the pause button is checked will pause right before the first user instruction - on the do_list is run. If the running Job executes a Robot.debugger + on the do_list is run. If the running Job executes a Control.debugger instruction, that will also cause the job to pause.

        When a job is paused, clicking the button will cause @@ -768,16 +766,16 @@
        Step 3
        pane, and pause. If a job is paused, and you uncheck the pause checkbox, then click the button, the job will begin running normally.

        -
        debugger - The instruction of Robot.debugger() in a do_list will cause +
        debugger + The instruction of Control.debugger() in a do_list will cause the pause checkbox to be checked and the job to pause. - A function that returns the result of Robot.debugger() or - even an array that has an element of the result of Robot.debugger() + A function that returns the result of Control.debugger() or + even an array that has an element of the result of Control.debugger() will cause a pause.

        - You can insert this instruction using the Learn JS menu/Debugging etc/Robot.debugger(). + You can insert this instruction using the Learn JS menu/Debugging etc/Control.debugger().

        - Note that using Robot.debugger() + Note that using Control.debugger() is intentionally similar to using the JavaScript constant debugger. If you have the constant debugger in the body of a function that is a do_list item, or is called by @@ -1073,7 +1071,7 @@
        Step 3
        can run on all robots. These are control instructions because they help manage the flow of instructions in a job. Examples include: Robot.stop to end a job immediately without processing the insructions after the stop instruction, - and Robot.wait_until which pauses execution of the job's instructions until + and Control.wait_until which pauses execution of the job's instructions until the calling of the given JS function returns true.

        See submenu "robot" for details about robots as @@ -1413,10 +1411,10 @@ so that you can distinquish some outputs from others in a complex debugging session. See out.
      • -
      • Robot.out Robot.out("some data") creates an +
      • IO.out IO.out("some data") creates an instruction in a Job's do_list to print content to the output console with similar functionality to out - See Robot.out.
      • + See IO.out.
      • show_instructions new Job({show_instructions=some_fn}) lets you call an arbitrary function (with clever defaults) before each diff --git a/doc/mental_model_of_memory.html b/doc/mental_model_of_memory.html index b941695e..293c572d 100644 --- a/doc/mental_model_of_memory.html +++ b/doc/mental_model_of_memory.html @@ -36,7 +36,7 @@

        A Mental Model of Memory

        Jobs can even modify each other, not just accidentally, but on purpose. A "manager" Job can tell a "worker" job what to do by adding an instruction to its do_list via -the instruction Robot.send_to_job. +the instruction Control.send_to_job.

        Dangerous and Productive
        Isn't this dangerous? Yep, just like a phone that can call any number, diff --git a/doc/processor_board_jumpers.png b/doc/processor_board_jumpers.png deleted file mode 100644 index 27849e28..00000000 Binary files a/doc/processor_board_jumpers.png and /dev/null differ diff --git a/doc/ref_man.html b/doc/ref_man.html index 8164e457..5c9c96d0 100644 --- a/doc/ref_man.html +++ b/doc/ref_man.html @@ -647,7 +647,7 @@ is useful for learning and when you don't need to extend a Job's do_list.

        - If the function arg to Robot.wait_until returns false when it is called + If the function arg to Control.wait_until returns false when it is called by the job running process, the Job continues waiting. Hit the ESC key on the keyboard to end the wait_until. Because Gamepad.down_keys() @@ -657,8 +657,8 @@
        new Job({
           name: "gamepad_job1",
        -  do_list: [Robot.out("Press RIGHT arrow or ESC to exit."),
        -            Robot.wait_until(function(){
        +  do_list: [IO.out("Press RIGHT arrow or ESC to exit."),
        +            Control.wait_until(function(){
                       let dks = Gamepad.down_keys() //returns array of down key objects
                       if (dks.length == 0){ return false }  //keep waiting
                       else if (dks[0].keyname == "ESCAPE"){ //end the "wait_until"
        @@ -674,7 +674,7 @@
                         return false //keep waiting
                       }
                     }),
        -            Robot.out("done")
        +            IO.out("done")
                    ]})
@@ -956,13 +956,7 @@ "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"}) Another picture displayed in "mycan".

- Robot Instruction
- There is a robot instruction with the same arguments and functionality as the above - method that you can stick directly on the do_list of a job.
- Example: -
new Job({name: "my_job",
-         do_list: [Robot.show_picture()]})
-      
+ There is a corresponding instruction, IO.show_picture
@@ -1015,7 +1009,7 @@
-
show_video +
show_video In order to take a picture for machine vision, you need to set up a video stream and take a snapshot of it. This is quite difficult using web-based sofware so we've made a wrapper for it.
@@ -1074,13 +1068,7 @@ "https://youtu.be/JAiHGA_I-P4"}) -Robot Instruction
-There is a robot instruction with the same arguments and functionality as the above -method that you can stick directly on the do_list of a job.
-Example: -
new Job({name: "my_job",
-     do_list: [Robot.show_video()]})
-      
+There is a corresponding instruction IO.show_video
take_picture @@ -1100,23 +1088,8 @@
 Picture.take_picture() 
 Picture.take_picture({callback: inspect}) 
-Robot Instruction
-There is a robot instruction with the same arguments and functionality as the above -method that you can stick directly on the do_list of a job. -

-The callback is called with this bound to the job -this instruction is running in.
-If the callback is a string, it is considered to be the name of -a user_data variable that you want to set to the mat created -with take_picture. You can then use this mat in further instructions. -
-Example: -
new Job({name: "my_job",
- do_list: [Robot.take_picture({
-             callback: "my_pic"})]})
-  
- Job.my_job.user_data.my_pic
- returns the mat. + +There is a corresponding instruction IO.take_picture
Low Level @@ -2043,14 +2016,17 @@

Midi.init initializes Midi. This is automatically done when you attempt to play a note -and Midi has not been inited, but there will be a slight delay in that case. +and Midi has not been inited, but there will be a slight delay in that case.
+Example:
+Midi.init()
+ If Midi has previously been inited, this calls Midi.all_notes_off. -This can also be called by choosing Insert menu/Sound/Midi.init(). +This can also be called by choosing Insert menu/Sound/Midi.init().
Parameters: None.

Midi.all_notes_off Turns off all curently on notes on all channels. Does not work when using -GarageBand for a synthesizer. +GarageBand for a synthesizer.
Parameters: None.

Midi.describe_midi_event @@ -2945,9 +2921,14 @@
Web
get_page - get_page retrieves the contents of a web page. - You pass in a url and get_page returns the contents as a string. - The argument can either be a string of a url, or a literal JS object.
+ get_page retrieves the contents of a web page.
+ Parameter:
+ url_or_options The url whose contents this instruction gets.
+ Instead of a url as the argument, you can pass a literal JS object with lots of options, described at: + options + These include the HTTP methods of: GET (the default), POST, PUT, PATCH, DELETE, HEAD, OPTIONS, + plus a zillion other options for authentication, etc. +

Example:
get_page("http://ibm.com")
If there is an error, the returned string will start with "Error:" @@ -2969,8 +2950,17 @@ a callback is called and passed an error object, a response object, and the body string of the page.

- It will not eval scripts on the page - so you may see an error when retriving a page containing JavaScript. + Parameters:
+ url_or_options The url whose contents this instruction gets.
+ Instead of a url as the argument, you can pass a literal JS object with lots of options, described at: + options + These include the HTTP methods of: GET (the default), POST, PUT, PATCH, DELETE, HEAD, OPTIONS, + plus a zillion other options for authentication, etc.
+ callback When the content of the web page is retrieved, + this callback is called, passing an error (or not), the response object, + and the string of the content of the page. + JavaScript on the page will not be evaled, but if the page is not found, + there will be an error.

Example:
 get_page_async("http://ibm.com",
   function(err, response, body){
@@ -3034,7 +3024,7 @@
 
new Job({name: "my_job",
          robot: Robot.dexter0,
          do_list: [ Dexter.move_to([0, 0.5, 0.075]),
-                    Robot.wait_until(2)
+                    Control.wait_until(2)
                     //...
                    ]
         })
@@ -3047,7 +3037,7 @@
  • Clicking on a running Job's button, i.e. in the Output pane
  • Calling Job.my_job.stop_for_reason("interrupted", "why you want it stopped")
  • In a running Job, executing the instruction: - Robot.stop_job()
  • + Control.stop_job()
You can stop all jobs by:
    @@ -3058,17 +3048,175 @@ Stopped jobs may be restarted by calling Job.my_job.start() again. This reinitializes the job with the original arguments passed to new Job and runs the do_list instructions again. +

    Webinar videos for Job:
    Making Jobs
    Advanced Jobs
    +
    Definition Time vs Run Time + To fully understand how a job operates, you must understand the difference + between Job Definition Time and Job Run Time. +

    + Job Definition Time
    + When you select and evaluate the source code of a job, you are + defining the Job. This simply uses JavaScript's eval + function to evaluate the source code, just like evaling any source code. + The result of evaling
    + new Job({name: "my_job" ...})
    + is an instance of the + class Job. Once done, you can access the Job instance via + Job.my_job
    + Many properties of the job are accessible via, for example, + Job.my_job.name , which returns the string of the Job's name.
    + Of particular import is the do_list. In the case of: +
    var ang1 = 90
    +new Job({name: "my_job",
    +         do_list: [Dexter.move_all_joints(ang1)]
    +         })
    + , evaluation sets the global variable ang1 to 90.
    + Then it sets Job.my_job to the new Job instance. + Within this instance, the property Job.my_job.do_list will + be effectively set to an array of instructions. + We say "effectively" because the actual do_list isn't bound + until the job is started. It is cached away so that it + can be copied afresh for each starting of the job. + But in any case, the do_list will now be an array containing + just one instance of the move_all_joints instruction. + This instance will have its joint1 angle set to 90 because ang1 + is evaled when the Job definition is evaluated, and ang1 evals + to 90. If you simply eval Dexter.move_all_joints(90), + you can inspect the returned object and see that it has an + array_of_angles property storing where the Dexter's angles will + be commanded to go when the Job is actually run. +

    + Job Run time
    + Now let's walk through Job Run Time. You run a Job + by clicking its button or by calling + Job.my_job.start()
    + One way to think of starting a job is that it is a second evaluation + of the Job. Only this time, it is not using JavaScript's eval, it is using + Job's special evaluator. Instead of taking an input of JavaScript source code, + its taking input of the items on the do_list (created via JS eval when defining the Job) + which can be of many different types. + Each instruction is "evaluated" by the Job differently, depending on its type. + (similar to JavaScript's eval!) + In the case of an instance of the move_all_joints instruction, + that instance is transformed to a lower level instruction + that is very similar to what you get when evaluating + make_ins("a", 90), then the 90 is convertered from + degrees into arcseconds, and sent to the Job's default robot, which better be a Dexter. +

    + Re-Running a Job
    + After a job has finished, you can rerun it by clicking its + button or calling its start method again. + This does not redefine the job. + So for instance, if between two runnings of a Job, we set our + global variable of ang1 to 45, + that will have no effect on + the 2nd running of our job, because we are not re-evaluating + ang1 or even + Dexter.move_all_joints(ang1). + Nor are we re-defining my_job. + That Joint1 inside the instance of move_all_joints is still 90, + so our 2nd running of my_job should behave the same, even + if we modify the value of ang1. + Actually if you do re_run my_job, it won't be exactly the same + because the Dexter robot will now be at a pose with Joint 1 equal to 90, + so in our 2nd running, the robot won't move. But besides this initial + "set up move", following moves would be the same between the two runnings. +

    + Functions on the do_list
    + The most flexible of instruction types is a JavaScript function. + This allows you to execute arbritary JavaScript at Job Run Time. + You might need to do this because you want to use the current environment + to help determine what instruction to run next. That current environment + may not be knowable at Job Definition Time. Perhaps you want to + verify that Dexter really is where you told it to go, or + you want to check on the status of some other Job before + proceeding with this one. +

    + To run arbitrary JS code during the running of a Job, wrap that code + in a function definition like so: +
    new Job({name: "my_job",
    +        do_list: [function(){return Dexter.move_all_joints(ang1)}]
    +        })
    + We could also do: +
    function move_it_buddy(){return Dexter.move_all_joints(ang1)}
    +new Job({name: "my_job",
    +         do_list: [move_it_buddy]
    +        })
    + particulary helpful for long functions or functions we want to + call multiple times. +

    + We are not doing: + do_list: [move_it_buddy()] because + we don't want to CALL move_it_buddy, as that would put its result on + the do_list at define time. We just want to eval the global variable + move_it_buddy to put its value, a function definition, onto the do_list + at define time. +

    + When a JavaScript function is called, + including when a running Job calls a function definition on its do_list, + it sets local variables of the function's parameters to the values passed in, + and evaluates the code in the body of the function. +

    + In the cases above, ang1 will be evaled at Job Run Time, + as will the code that creates the instance of move_all_joints. + Anything returned by a function definition on the do_list will be + dynamically added to the do_list right below the function definition. + Our do_list started out with just one item, but after running + that one item, our do_list is extended by an instance of move_all_joints. + That instance of move_all_joints is inserted into the do_list immediately after + the function that generated it, making it run immediately after the function. + So now, if we change the value of ang1 between two runnings of my_job, + we won't have to redefine the whole job in order to get the effect + of changing the value of ang1. +

    + A function definition on a do_list doesn't + have to return anything. It may just have side effects like calling out + for a print statement. But if it returns something that is not a valid instruction, + the Job will error. +

    + Job Instance Access
    + If you want to access the running Job Instance at run time, use a + function definition on the do_list, and reference this in + the body of that function definition.
    + Example:
    +
    new Job({name: "my_job",
    +         do_list:[function(){ this.user_data.foo = 2},
    +                 function(){ out("foo: " + this.user_data.foo +
    +                                 ", robot: " + this.robot.name) }
    +                 ]})
    +    
    + Note that using user_data is a convenient way to pass values from one + instruction to another, without cluttering the global variable space.
    + Flexibility and Complexity
    + Jobs have a great deal of flexibility. We can use the full power of + a general purpose programming language plus DDE's libraries + to define them. During their + running, Jobs can also use the the full power of + a general purpose programming language plus DDE's libraries, or any + that you import or create. +

    + Though powerful, this flexibility comes + at the cost of complexity. Using JavaScript's function defining + capability, you can hide (but not eliminate) many details. + Also, DDE attempts to make writing JavaScript and Jobs as simple as + possible, but it is still not simple, especially if you want + to understand what's going on beneath the surface. The goal of Dexter + is to enable you to make as many things as possible. This is why DDE needs to be so flexible. +

    + For a more complete understanding of JavaScript eval, see the DDE article: + How to Think like a Computer +
    +
    new Job parameters The parameters used to define a new Job all have defaults.
    new Job()
    is a valid job to define and run, though it will give a warning message:
    Warning: While starting job: job_1, the do_list is empty.
    - The job still requests the status of Dexter but does not cause it to move.

    + The job still requests the status of Dexter, but does not cause it to move.
    You can customize a job with the parameters:
    1. name Default's to an unused name of the format: "job_1", "job_2", "job_3", etc. You can use Job.a_job_name to get the instance programmatically.
    2. @@ -3098,15 +3246,15 @@ Normally a Job definition will look something like:
      
       new Job({name: "my_job",
      -      do_list: [Robot.out("hi")]})
      +      do_list: [IO.out("hi")]})
             
      - The call to Robot.out is evaled when the call to new Job is evaled. - Robot.out("hi") evals to an instruction object. - (Go ahead and select the call to Robot.out and click + The call to IO.out is evaled when the call to new Job is evaled. + IO.out("hi") evals to an instruction object. + (Go ahead and select the call to IO.out and click .) It is that object which is put on the do_list. - If that call to Robot.out was inside a function and returned, - and that function was on the do_list, the result of the Robot.out + If that call to IO.out was inside a function and returned, + and that function was on the do_list, the result of the IO.out would be put on the do_list. If instead we had put on our do_list:
      
      @@ -3214,7 +3362,7 @@
                the initial instructions when running the job.
                This is most commonly a non-negative integer but
                you can use any instruction_location here.
      -        See Robot.go_to
      +        See Control.go_to
               for documentation on all the different kinds of instruction_locations.
               
               
    3. ending_program_counter Default: "end" Declare at the outset of a job that it @@ -3222,7 +3370,7 @@ valid instruction_location that does not include a job. It is evaluated at the start of each instruction, so if labels move as a result of dynamically added instructions, that's OK. - See Robot.go_to + See Control.go_to for documentation on all the different kinds of instruction_locations.

      Use program_counter and ending_program_counter to "play" just a segment @@ -3254,21 +3402,32 @@ A function When the job reaches its normal stopping point, the function is called with a 'this' of the job instance and no arguments. This is effectively a callback when the job is done.
      - An instruction location This is similar to Robot.go_to + An instruction location This is similar to Control.go_to except that the job is re-initialized with its original (as defined) do_list. Any valid instruction location can be used - (see doc in Robot.go_to) except + (see doc in Control.go_to) except that it must have no job or indicate the current job. Using 0 set's the job's program_counter to instruction 0, causing the job to loop. Using an instruction location 'offset' of greater than 0, such as a label, will cause the initial running of the job to execute its top instructions, but then subsequent iterations will start somewhere in the middle.
      - A Robot.stop_job instruction can be used to end a loop. + A Control.stop_job instruction can be used to end a loop.

      See the menu item: Jobs/Insert Example/when_stopped
    4. callback_param An advanced feature of start_object, not normally used.
    + All of these parameters are accessible as properties from the job instance that they help create.
    + Example:
    + Job.my_job.robot gets the default robot for the job.
    + To access these properties from an instruction in a running job, use a function definition + on the do_list. When a running job encounters a function definition on its do_list, + it calls that function with no arguments, but with a this of + the job instance. From that you can get the Job's properties like so:
    +
    new Job ({name: "my_job",
    +          do_list: [function(){out(this.robot.name)}]})
    + where this refers to Job.my_job. + When run, my_job prints its default robot's name to the Output pane.
    start @@ -3285,7 +3444,7 @@
    • program_counter Initialize the program_counter to an instruction id. The default is 0. You can use any instruction_location here. - See Robot.go_to + See Control.go_to for documentation on all the different kinds of instruction_locations.
    • initial_instruction Add an initial instruction to the do_list at @@ -3308,15 +3467,15 @@ instruction The instruction to insert. This can be an array of instructions if you effectively want to insert more than one.
      location Any instruction_location documented under - Robot.go_to literal can be used + Control.go_to literal can be used including integers, names of labels, etc.
      -Example:
       Job.insert_instruction(Robot.out("the end"),
      +Example: 
       Job.insert_instruction(IO.out("the end"),
                              {job: "my_job",
                               offset: "end"})

      Adds an instruction that prints "the end" at the end of Job.my_job.do_list.
      -The instruction Robot.send_to_job performs the core of this functionality +The instruction Control.send_to_job performs the core of this functionality in an instruction format.
      -See Robot.wait_until documentation for its "new_instruction" argument +See Control.wait_until documentation for its "new_instruction" argument on how to continually add and execute a new instruction in a running job.
    @@ -3328,6 +3487,15 @@ A robot is a machine that can execute the instructions in its instruction set. In general, each kind of robot will have its own instruction set. The primary difference between robots is the difference in their instruction sets. +

    + All Jobs have a default robot property. You can access the robot property + from a running job like so:
    +
    new Job ({name: "my_job",
    +          do_list: [function(){ this.robot }]})
    + where this refers to Job.my_job.
    + You might use this to print out the current robot's name, by replacing
    + this.robot with:
    + out(this.robot.name)
    Robot Instructions These instructions can be used for any robot, as can the instructions for @@ -3342,7 +3510,7 @@ Arrays may be nested to any depth.
    Example:
    new Job({name: "Job1",
    -         do_list: [[Dexter.move_all_joints([10, 20, 0, 0, 0]), Robot.out("did it")],
    +         do_list: [[Dexter.move_all_joints([10, 20, 0, 0, 0]), IO.out("did it")],
                         function(){
                             let result = []
                             for(let ang = 0; ang <= 90; ang += 15){
    @@ -3427,14 +3595,14 @@
     }
     
    The above example could better be implemented using -Robot.wait_until, but there are +Control.wait_until, but there are cases where the increased flexibility of the above is convenient.

    You can put both named and anonymous functions on a do_list. A named instruction allows you to add a bit of "documentation" to the function. You can also use the name of a function like a label and "go to" -that name using a Robot.go_to instruction, +that name using a Control.go_to instruction, or use the function's name in any uses of an instruction_location.

    @@ -3449,9 +3617,9 @@ If you have some JavaScript code that you want to go between 2 other instructions on a Job's do_list, your first inclination might be to just stick it on the do_list like so -
     do_list: [Robot.out("hi"),
    +
     do_list: [IO.out("hi"),
               my_fn(12, 34),
    -          Robot.out("bye")
    +          IO.out("bye")
               ]
     
    What happens here is that when the definition of the job is evaled, @@ -3463,7 +3631,7 @@ then you're all set.
    BUT if what you want is for the eval of my_fn(12, 34) -to occur after the instruction returned by Robot.out("hi") is run, +to occur after the instruction returned by IO.out("hi") is run, then you'll want to wrap your code in a function, and put the definition of that wrapper function on the do_list like so:
    function(){ my_fn(12, 34) }
    @@ -3519,9 +3687,9 @@ function* my_gen(){ while(true){ if(Math.random() > 0.2) { - yield Robot.out("my_gen still alive") + yield IO.out("my_gen still alive") } - else { return Robot.out("gotta go") } + else { return IO.out("gotta go") } } } new Job({name: "my_job", @@ -3531,7 +3699,7 @@ for code examples. Related Job facilities are:
    • using instruction -Robot.wait_until("new_instruction")
    • +Control.wait_until("new_instruction")
    • Job parameter: when_stopped
    @@ -3557,7 +3725,7 @@ when "instructions" on the do_list are started. DDE has a number of instruction types that help you control when the next instruction is run. A primary one is called -wait_until +wait_until that blocks the processing of the following instruction.

    In order to provide a more general mechanism to handle such methods @@ -3627,7 +3795,7 @@
    new Job({name: "Job1",
           do_list: [{start: function(){out("wait a sec or 3")},
                      dur: 3},
    -                 Robot.out("done")]})
    +                 IO.out("done")]})
           
  • user_data_variable Default: null If present and non-null, the automatically synthesized callback @@ -3640,7 +3808,7 @@
Examples:
new Job({name: "my_job1",
-         do_list: [Robot.out("hey from my_job1")]})
+         do_list: [IO.out("hey from my_job1")]})
 new Job({name: "my_job2",
          robot: new Brain({name: "brain2"}),
          do_list: [Job.my_job1]})
@@ -3673,7 +3841,7 @@
 new Job({name: "my_job",
          do_list: [{start: function(){out("wait a sec or 3")},
                     dur: 3},
-                   Robot.out("done waiting")]})
+                   IO.out("done waiting")]})
     
Above when the job starts, the first instruction's start method is called, which prints in the Output pane, "wait a sec or 3". @@ -3683,7 +3851,7 @@ and prints out "done waiting".
new Job({name: "my_job",
-    do_list: [Robot.out("hey"),
+    do_list: [IO.out("hey"),
     {start: speak,
      start_args: {speak_data: "Clean my work space.", rate:0.8},
      callback_param: "callback",
@@ -3694,7 +3862,7 @@
      callback_param: "callback",
      user_data_variable: "the_text2"
     },
-    Robot.out("you")
+    IO.out("you")
     ]})
     
The above job first prints out "hey", then speaks @@ -3718,7 +3886,7 @@ callback(b, a, 123) } new Job({name: "my_job", - do_list: [Robot.out("hey"), + do_list: [IO.out("hey"), {start: test_cb, start_args: [11, 22], callback_param: 2, @@ -3758,65 +3926,45 @@ they already were.
-
break - The Robot.break() instruction, when in the body of a - Robot.loop, +
Control + Control instructions can be used in any Job regardless of its default robot. +
break + The Control.break() instruction, when in the body of a + Control.loop, instruction, ends the loop.

Parameters: None.
Example:
new Job({name: "my_job",
-     do_list: [Robot.out("start of job"),
-               Robot.loop(true,
+     do_list: [IO.out("start of job"),
+               Control.loop(true,
                           function(iter_index, iter_val, iter_total){
                               if(iter_index < 3) {
-                                 return Robot.out("index: "       + iter_index +
+                                 return IO.out("index: "       + iter_index +
                                                   " iter_val: "   + iter_val +
                                                   " iter_total: " + iter_total)}
-                              else { return Robot.break() } }),
-               Robot.out("end of job")
+                              else { return Control.break() } }),
+               IO.out("end of job")
                ]})    
- If you have nested loops, Robot.break ends the inner loop only. - If Robot.break is not in a loop, a warning will be printed in the Output pane. + If you have nested loops, Control.break ends the inner loop only. + If Control.break is not in a loop, a warning will be printed in the Output pane.

Note that Javascript also uses the symbol break to end a JavaScript for loop.
- +
debugger - Robot.debugger() allows you to pause and step through the rest of the instructions. - See Robot.debugger + Control.debugger() allows you to pause and step through the rest of the instructions. + See Control.debugger
-
error +
error Causes the job to error, immediately stopping it. The one argument is a string of a reason for the error which - will aid in debugging. Example:
Robot.error("fubar")
+ will aid in debugging. Example:
Control.error("fubar")
-
get_page - Similar to the function get_page, only made into an instruction. - This instruction gets the content of a url and places it in a given user_data variable - on the job instance before allowing the next instruction in the do_list to run.
- Example:
new Job({name: "j1",
-         do_list: [Robot.get_page("http://www.nactem.ac.uk/software/acromine/dictionary.py?sf=BMI"),
-                   function(){out("got response of: " + 
-                                    this.user_data.http_response)}
-         ]}) 
- Parameters:
- url_or_options: The url whose contents this instruction gets.
- Instead of a url as the argument, you can pass a literal JS object with lots of options, described at: - options -

- response_variable_name: Default: "http_response". The name - of the property in the job instance's user_data object to set to the - content of the url. -

-Using make_url can help make constructing calls to get_page a bit easier. -Example:
 Robot.get_page(make_url("www.nactem.ac.uk/software/acromine/dictionary.py",
-                         {sf: "BMI"}))
-make_url is documented in the reference manual under "I/O". -
-
go_to + +
go_to In the 1960's programs were usually characterized by a list of instructions. As more flexible control was needed, the "goto" instruction was added to cause the program_counter that was pointing at the executing instruction to jump to @@ -3881,7 +4029,7 @@ Generally "highest_completed_instruction_or_zero" is more useful than "highest_completed_instruction".
-A literal object The literal object can have up to three properites. +A literal object The literal object can have up to three properites.
  • offset Can be any one of the above instruction_locations.
    Example: {offset: 12}
  • @@ -3922,136 +4070,27 @@
Instruction_locations are also used in initializing a Job's program_counter in defining the job, as well as -in calling a job's start method. The Robot.send_to_job instruction +in calling a job's start method. The Control.send_to_job instruction uses an instruction_location for identifying where within the job to insert the given instruction as does the method Job.insert_instruction.

Much of the time that you'd be tempted to use a backwards - Robot.go_to, - Robot.loop is better. + Control.go_to, + Control.loop is better.
- -
grab_robot_status - robot_status is an array that holds data from the robot, along with aspects of the instruction - that was sent to the robot to capture that data. This array is the value of the robot - instance field robot_status.

- The instruction: Robot.grab_robot_status() provides an easy way to get values out of that array - and made available for other instructions to use.
- Parameters
- user_data_variable Default: "grabbed_robot_status" A string (required) of the name of the job's - user_data variable to shove the grabbed - robot_status data into. If there is already such a variable, - its value is over-written. If not, it is created and intialized - with the new value. -

- After this instruction is run, the grabbed value - is available globally Job.the_job_name.user_data.the_val_of_this_arg.
- For instance, if this job is named "my_job" and the value of - user_data_variable is "my_var" then: - Job.my_job.user_data.my_var - But inside another instruction that is defined as a function, - this will be bound to the current job instance so: - this.user_data.my_var will access the grabbed data.
- - start_index A non-negative integer signifying the - first data item to extract from the robot_status array. - Its default value is the value of Serial.DATA0 - Another normal value would be Dexter.J1_ANGLE - See below for a comprehensive list. -
- Special values:
- "all" The entire 60 element robot_status - array is used. The value of end_index is ignored.
- "data_array" The part of the robot_status array after - the header information is used. For Serial robots, this will be just - the data returned by the serial port. For Dexter robots, this - will be the data about Dexter's joint angles, etc. - The value of end_index is ignored.
- end_index A non-negative integer signifying the last element of the - robot_status array to extract. The default value is null. - If this value is null or not passed, only the data at the start_index - is used. The value stored in the user_data variable will NOT be an array, - just that one element.
- Special value "end" The portion of the robot_status - from start_index through the end of the robot_status array is extracted. -

- If both the start_index and end_index are not passed, - then the value will be: For a Serial robot, the first "item" - returned from the serial port in response to a send. - For Dexter, it will be DMA_READ_DATA. -

- You can use some constants for the values of start_index and end_index.
- For a Dexter robot these are:
- Dexter.JOB_ID
- Dexter.INSTRUCTION_ID
- Dexter.START_TIME //ms since jan 1, 1970 from Dexter's clock
- Dexter.STOP_TIME
- Dexter.INSTRUCTION_TYPE //a letter signifying the instruction type.
-
- Dexter.ERROR_CODE //0 means no error.
- Dexter.DMA_READ_DATA
- Dexter.READ_BLOCK_COUNT
- Dexter.RECORD_BLOCK_SIZE
- Dexter.END_EFFECTOR_IN
-
- Dexter.J1_ANGLE
- Dexter.J1_DELTA
- Dexter.J1_PID_DELTA
- Dexter.J1_FORCE_CALC_ANGLE
- Dexter.J1_A2D_SIN
- Dexter.J1_A2D_COS
- Dexter.J1_PLAYBACK
- Dexter.J1_SENT
- Dexter.J1_SLOPE
-and a similar set of names beginning with J2, J3, J4 and J5. -

-For a Serial robot these are:
- Serial.JOB_ID
- Serial.INSTRUCTION_ID
- Serial.START_TIME // ms since jan 1, 1970 from Dexter's clock
- Serial.STOP_TIME // ms since jan 1, 1970 from Dexter's clock
- Serial.INSTRUCTION_TYPE // A letter indicating the instruction type
- - Serial.ERROR_CODE // means no error.
- Serial.DATA0 // data coming back from the board
- Serial.DATA1
- Serial.DATA2
- Serial.DATA3
- Serial.DATA4
- Serial.DATA5
- Serial.DATA6
- Serial.DATA7
- Serial.DATA8
- Serial.DATA9
-
- -In order to insure that the Dexter robot_status array is up to date, -you should place Dexter.get_robot_status() in your do_list before -the grab_robot_status instruction.
-Examples of 2 adjacent instructions on a job's do_list:
- - Dexter.get_robot_status()
- Robot.grab_robot_status("my_zero_data", Serial.DATA0)
-grab_robot_status can be called with a subject of a job instance in place -of Robot like so:
- Robot.dexter0.grab_robot_status("my_user_data_var") -

-See Job Example: Serial Port for an extended example. -
- -
if_any_errors - Robot.if_any_errors checks to see if any of the supplied jobs have errored. +
if_any_errors + Control.if_any_errors checks to see if any of the supplied jobs have errored. If so, it inserts the supplied instruction into the do_list on the job - that this instruction is on. In either case, Robot.if_any_errors continues + that this instruction is on. In either case, Control.if_any_errors continues the job that its on. Parameters
job_names An array of strings of job names. If a named job doesn't exist, error.
- instruction_if_error Default: Robot.error() An instruction that will be inserted into the do_list + instruction_if_error Default: Control.error() An instruction that will be inserted into the do_list immediately after this instruction IF one of the named jobs has errored. The default for instruction_if_error is a function that - inserts an instance of Robot.error into the do_list of this job, causing it to error. + inserts an instance of Control.error into the do_list of this job, causing it to error. The "reason" for that error instruction is explicit about the cause of the error.

Generally a job should be stopped quickly if there is an error. @@ -4060,13 +4099,13 @@ current job for "this") so that some arbitrary compensation can be made for the error.
-
include_job +
include_job Inserts a do_list or a portion thereof into the current Job, right after this instruction.
The job_name parameter takes a wide variety of data to specify where to get a do_list from. Most of those data formats are not actual Job names, but they perform a similar role to a Job name in referencing a do_list to include.

-Robot.include_job has a large number of very specific error messages to help you correct mistakes.
+Control.include_job has a large number of very specific error messages to help you correct mistakes.
Parameters:
job_name Required. Indicates a do_list, which will be "clipped" by start_loc and end_loc. Formats: @@ -4082,7 +4121,7 @@ instead of Jobs or Arrays is that the string is resolved at instruction run-time whereas Job and Array are resolved at Job definition time. By delaying the resolution, you can place the definition that the string refers to - AFTER the Job definition containing the Robot.include_job instruction. + AFTER the Job definition containing the Control.include_job instruction. String formats:
  • Job.a_job_name Example: "Job.my_job"
    This is the prefered way to reference a Job. @@ -4130,7 +4169,7 @@ If job_name refers to a Job, the default is the Job's orig_args.program_counter, whose default is, in turn, 0 When job_name refers to a job, start_loc can be any 'instruction_location'.
    - See Robot.go_to for details + See Control.go_to for details on the instruction_location format.
    end_loc Grab the instructions from the indicated do_list, up to, but not including, end_loc. If job_name refers to an instructions array, the default for end_loc is @@ -4140,28 +4179,28 @@ When job_name refers to a job, end_loc can be any 'instruction_location', and its default is the ending_program_counter for the job, which is, by default, "end".

    - See Robot.go_to for details + See Control.go_to for details on the instruction_location format.

    Example:
    - Robot.include_job("Job.my_job")
    + Control.include_job("Job.my_job")
    Inserts all the original instructions that are normally run when running "my_job", into the current job, right below this instruction.

    - Robot.include_job makes it easy to build up jobs hierarchically, effectively + Control.include_job makes it easy to build up jobs hierarchically, effectively treating a low level Job as a single instruction in a higher level Job.
    - See also: Robot.start_job + See also: Control.start_job
-
label +
label This instruction is a no-op. However, it provides a target location -for Robot.go_to, initializing the program_counter of a job, and -a location to insert an instruction using Robot.send_to_job.
-Example: Robot.label("lab2") +for Control.go_to, initializing the program_counter of a job, and +a location to insert an instruction using Control.send_to_job.
+Example: Control.label("lab2")
-
loop - Robot.loop lets a Job repeat a sequence of instructions a number of times. +
loop + Control.loop lets a Job repeat a sequence of instructions a number of times. This instruction is one of the most powerful and complex in DDE, providing a wide range of looping behavior.
Parameters (2)
@@ -4169,14 +4208,14 @@ i.e. the iteration count of the loop. It can be one of five types:

boolean
If false, loop no times. This loop becomes a no-op.
- If true, loop forever, or until a Robot.break instruction in the body_fn + If true, loop forever, or until a Control.break instruction in the body_fn is executed. Like all arguments, this is evaluated at Job definition time to supply a value of a boolean (true or false).

non-negative integer
Number of times to call the body_fn. If 0, the loop becomes a no-op, just like when this arg is false. This can be Infinity such that looping would only - be limited by a Robot.break instruction in the body_fn or + be limited by a Control.break instruction in the body_fn or stopping the Job.

array
The length of the array is the number of times to loop. @@ -4209,11 +4248,11 @@ no looping occurs, this loop is a no-op. If true is returned, that "true" is used for the first iteration, and the function is called at the beginning of every subsequent iteration until false - is returned or the body_fn executes a Robot.break instruction. + is returned or the body_fn executes a Control.break instruction. You want to use a function here instead of directly using a boolean, non-neg-integer, or array, when you want those values to be computed at the time - the Robot.loop instruction is executed as opposed to Job definition time. + the Control.loop instruction is executed as opposed to Job definition time.

body_fn This function is called with basically the same args as are used to call times_to_loop when it is a function.
@@ -4248,69 +4287,32 @@ a number 6 and generally appear before normal string-named keys. Its best if you don't depend on an ordering of the properties. - Loop instructions may be nested. Robot.break instructions will - end the inner-most loop that contains the Robot.break. + Loop instructions may be nested. Control.break instructions will + end the inner-most loop that contains the Control.break.

Much of the time that you'd be tempted to use a backwards - Robot.go_to, + Control.go_to, loops are better.

Example
new Job({name: "my_job",
-    do_list: [Robot.out("start of job"),
-              Robot.loop([100, 101, 102, 103, 104],
+    do_list: [IO.out("start of job"),
+              Control.loop([100, 101, 102, 103, 104],
                          function(iter_index, iter_val, iter_total){
                              if(iter_index < 3) {
-                                return Robot.out("index: "       + iter_index +
+                                return IO.out("index: "       + iter_index +
                                                  " iter_val: "   + iter_val +
                                                  " iter_total: " + iter_total)}
-                             else { return Robot.break() }
+                             else { return Control.break() }
                          }
                         ),
-               Robot.out("end of job"),
+               IO.out("end of job"),
 ]})     
- See Jobs menu/Insert Example/Robot.loop for more examples. + See Jobs menu/Insert Example/Control.loop for more examples.
-
out - Very similar to the function out but made for use as a robot instruction. - Prints a message to the Output pane in the color of your choice. - This is the "print statement" as an instruction, so useful in debugging.

- Example:
- Robot.out("hey " + 345, "green")

- Parameters:
- val Any JavaScript data. Most commonly this is a string, but - can be a number, an array etc.
- color A string representing a color. Default "black". Can - be any string in the color series, or have the format:
- "rgb(255, 100, 0)".
- temp Default: false. With the default, printed output stays in the - Output pane until DDE is relaunched. -

- If temp is true, - then val is still printed in the Output pane, but is over-ridden - by subsequence calls to out with temp == true. Calls to out with temp == false - will erase any "temp" output. -

- If temp is a string, then out prints val similarly to temp == true in that - the previous call to out with that temp string will be over-ridden in place. - However, when another call to out with a different value for temp occurs, - the output of out with a temp string will not be erased. - Thus the last call to out with a given temp string remains in the Output pane, - in its place, regardless of other out calls. -

- This is the most basic debugging tool, often used to let you know that - the job its in has reached this instruction. Unlike an instruction such as:
- function(){out("The status is: " + this.status_code)}
- Robot.out cannot access its job instance, but it does print out - its job name and instruction ID right before printing val. -

- See also print statements -
- - -
start_job - Robot.start_job starts another job.
+
start_job + Control.start_job starts another job.
Parameters:
job_name Required. Can be:
  • An instance of the Job to start.
  • @@ -4366,23 +4368,23 @@
    
     new Job({name: "job1",
    -         do_list: [Robot.out("in job 1"),
    -                   Robot.wait_until(5)]})
    +         do_list: [IO.out("in job 1"),
    +                   Control.wait_until(5)]})
     new Job({name: "job2",
              robot: new Brain({name: "b1"}),
    -          do_list: [Robot.start_job("Job.job1"),
    -                    Robot.out("last instruction")
    +          do_list: [Control.start_job("Job.job1"),
    +                    IO.out("last instruction")
                         ]
     })    
    job2 starts job1 and waits for job1 to complete before continuing, because the last arg to start_job is true.
    
     new Job({name: "job1",
    -         do_list: [Robot.out("in job 1"),
    -                   Robot.wait_until(5)]})
    +         do_list: [IO.out("in job 1"),
    +                   Control.wait_until(5)]})
     new Job({name: "job2",
    -          do_list: [Robot.start_job("job1", undefined, undefined, true),
    -                    Robot.out("last instruction")]
    +          do_list: [Control.start_job("job1", undefined, undefined, true),
    +                    IO.out("last instruction")]
     })    
    job2 starts job1 and waits for job1 to complete before continuing, because the last arg to start_job is true. @@ -4392,17 +4394,17 @@ neither can rely on the Dexter being where they last told it to go.
    
     new Job({name: "job1",
    -         do_list: [Robot.out("in job 1"),
    -                   Robot.wait_until(5)]})
    +         do_list: [IO.out("in job 1"),
    +                   Control.wait_until(5)]})
     new Job({name: "job2",
              robot: new Brain({name: "b1"}),
    -          do_list: [Robot.start_job("job1", {start_if_robot_busy: true}, undefined, true),
    -                    Robot.out("last instruction")]
    +          do_list: [Control.start_job("job1", {start_if_robot_busy: true}, undefined, true),
    +                    IO.out("last instruction")]
     })    
    - See also Robot.include_job + See also Control.include_job
-
stop_job +
stop_job Causes the indicated job to stop just before executing a particular instruction.
Parameters:
instruction_location Optional. Determines the job to stop and the instruction within that @@ -4412,18 +4414,18 @@ the offset will be "program_counter", i.e. the job will stop before executing its next instruction. Thus the default is that the current job will be stopped immediately. - See Robot.go_to for details + See Control.go_to for details on the instruction_location format.
reason A string. The reason that the job is being told to stop.
perform_when_stopped A boolean. When false (the default) the job will behave as if its 'when_stopped' parameter is "stop", i.e. the job will end. When true, the job's when_stopped action will be performed.
- Example:Robot.stop_job({job: "my_job", offset: "label1"}) + Example:Control.stop_job({job: "my_job", offset: "label1"}) Job.my_job will stop when it reaches the instruction named "label1".
-
suspend +
suspend Causes the indicated job to pause. It will not execute any more of its instructions until it is unsuspended.
Parameters:
@@ -4433,11 +4435,11 @@ May also be a job_instance to suspend.
reason Default: "". A string of why the job is suspended.
Example: - Robot.suspend().
+ Control.suspend().
Suspends the current job for no reason.
-
unsuspend +
unsuspend Unsuspends a suspended job. If the job to be suspended is not suspended, no action will be taken. Note that a job cannot unsuspend itself, because it is not running. @@ -4448,7 +4450,7 @@ If a string, the job is stopped, with the reason of this stop_reason.

Example:
-Robot.suspend("my_job")

+Control.suspend("my_job")

unsuspend is also a method on a job.
Example:
Job.my_job.unsuspend().
@@ -4458,7 +4460,7 @@
-
sync_point +
sync_point A sync_point instruction causes all running jobs with the same-named sync_point to pause until they all reach the sync_point. Once that happens, they are all allowed to proceed executing their instructions.
@@ -4470,7 +4472,7 @@ you want to all pause at the same place. A sync_point instruction may contain the name of the job its in. Effectly that doesn't matter, but does allow all the same-named sync points to have the same list of job_names.
- Example: Robot.sync_point("midway", ["job_one", "job_two"])
+ Example: Control.sync_point("midway", ["job_one", "job_two"])

If a sync point instruction has no job names, or only its own job name, it will not wait but rather breeze right through its sync_point. @@ -4484,11 +4486,11 @@ because it allows those jobs that can proceed to do so.
-
send_to_job -Robot.send_to_job inserts instructions into another job's do_list. It can also -start, unsuspend and grab data from another job. Robot.send_to_job is complex, powerful, +
send_to_job +Control.send_to_job inserts instructions into another job's do_list. It can also +start, unsuspend and grab data from another job. Control.send_to_job is complex, powerful, and rarely used. Example:
-Robot.send_to_job({do_list_item: Dexter.move_to([0, 0.5, 0.075]),
+Control.send_to_job({do_list_item: Dexter.move_to([0, 0.5, 0.075]),
                    where_to_insert: "end",
                    unsuspend: true,
                    wait_until_done: true})
@@ -4497,7 +4499,7 @@
  • where_to_insert The location in the do_list of the to_job to insert the do_list_item. The full list of instruction_locations for where_to_insert can be seen in the documentation for the - Robot.go_to instruction. + Control.go_to instruction. Particularly useful ones for send_to_job are:
    • {job: "other_job", offset: "next_top_level"} Inserts the do_list_item after the program_counter and just before the next original (top level) do_list item @@ -4545,13 +4547,13 @@ Job.insert_instruction.
  • - -
    wait_until +
    wait_until This instruction causes a job to pause until the supplied condition is met.
    -Example:Robot.wait_until(2)
    +Example:Control.wait_until(2)
    Parameter:
    fn_date_dur Default: 1 This argument can be one of:
    • an integer The number of seconds for the wait.
    • @@ -4587,19 +4589,19 @@ on the do_list, then this job waits until a new instruction is added on the end of a do list.
      Example:
       new Job({name: "my_job",
      -         do_list: [Robot.out("first instruction"),
      -                   Robot.wait_until("new_instruction")]})
      +         do_list: [IO.out("first instruction"),
      +                   Control.wait_until("new_instruction")]})
           
      Evaling Job.my_job.start()
      causes "first instruction" to be printed out, then the job pauses until an instruction is inserted into the do_list right after this "wait_until" instruction.
      - Job.insert_instruction(Robot.out("the end"),
      + Job.insert_instruction(IO.out("the end"),
                              {job: "my_job",
                               offset: "after_program_counter"})
      Causes wait_until to stop waiting, "the end" is printed and the job completes successfully.
      If instead of that argument to Job.insert_instruction, we had instead used:
      - Job.insert_instruction([Robot.out("the middle"),
      -                        Robot.wait_until("new_instruction")],
      + Job.insert_instruction([IO.out("the middle"),
      +                        Control.wait_until("new_instruction")],
                               {job: "my_job",
                                offset: "after_program_counter"})
      then each time we eval that code, it prints "the middle" and @@ -4613,26 +4615,240 @@ to refer to a program_counter that is less than or equal to the program_counter in the job referenced by the instruction_location. This instruction waits until a job gets to a certain instruction. - This performs like a one_sided Robot.sync_point instruction + This performs like a one_sided Control.sync_point instruction where the job this instruction is in will wait for the job specified in the instruction_location, but not the other way around. - Its advantage over Robot.sync_point is that you don't have - to have a Robot.sync_point instruction in the job you're waiting for. + Its advantage over Control.sync_point is that you don't have + to have a Control.sync_point instruction in the job you're waiting for. Thus you can wait for a job reaching one of its instructions that never expected it to be waited for.
      Warning: If the instruction_location specifies the job - that this Robot.wait_until instruction is in, or lets the instruction_location + that this Control.wait_until instruction is in, or lets the instruction_location default to that, this job could wait for itself and thus forever.
    +
    +
    IO + IO instructions can be used in any Job, regardless of its default robot. +
    get_page + Similar to the function get_page, only made into an instruction. + This instruction gets the content of a url and places it in a given user_data variable + on the job instance before allowing the next instruction in the do_list to run.
    + Example:
    new Job({name: "j1",
    +         do_list: [IO.get_page("http://www.nactem.ac.uk/software/acromine/dictionary.py?sf=BMI"),
    +                   function(){out("got response of: " +
    +                                    this.user_data.http_response)}
    +         ]}) 
    + Parameters:
    + url_or_options: The url whose contents this instruction gets.
    + Instead of a url as the argument, you can pass a literal JS object with lots of options, described at: + options + These include the HTTP methods of: GET (the default), POST, PUT, PATCH, DELETE, HEAD, OPTIONS, + plus a zillion other options for authentication, etc. +

    + response_variable_name: Default: "http_response". The name + of the property in the job instance's user_data object to set to the + content of the url. +

    + Using make_url can help make constructing calls to get_page a bit easier. + Example:
     Control.get_page(make_url("www.nactem.ac.uk/software/acromine/dictionary.py",
    +                         {sf: "BMI"}))
    + make_url is documented in the reference manual under "I/O". +
    + +
    grab_robot_status + robot_status is an array that holds data from the robot, along with aspects of the instruction + that was sent to the robot to capture that data. This array is the value of the robot + instance field robot_status.

    + The instruction: IO.grab_robot_status() provides an easy way to get values out of that array + and made available for other instructions to use.
    + Parameters
    + user_data_variable Default: "grabbed_robot_status" A string (required) of the name of the job's + user_data variable to shove the grabbed + robot_status data into. If there is already such a variable, + its value is over-written. If not, it is created and intialized + with the new value. +

    + After this instruction is run, the grabbed value + is available globally Job.the_job_name.user_data.the_val_of_this_arg.
    + For instance, if this job is named "my_job" and the value of + user_data_variable is "my_var" then: + Job.my_job.user_data.my_var + But inside another instruction that is defined as a function, + this will be bound to the current job instance so: + this.user_data.my_var will access the grabbed data.
    + + start_index A non-negative integer signifying the + first data item to extract from the robot_status array. + Its default value is the value of Serial.DATA0 + Another normal value would be Dexter.J1_ANGLE + See below for a comprehensive list. +
    + Special values:
    + "all" The entire 60 element robot_status + array is used. The value of end_index is ignored.
    + "data_array" The part of the robot_status array after + the header information is used. For Serial robots, this will be just + the data returned by the serial port. For Dexter robots, this + will be the data about Dexter's joint angles, etc. + The value of end_index is ignored.
    + end_index A non-negative integer signifying the last element of the + robot_status array to extract. The default value is null. + If this value is null or not passed, only the data at the start_index + is used. The value stored in the user_data variable will NOT be an array, + just that one element.
    + Special value "end" The portion of the robot_status + from start_index through the end of the robot_status array is extracted. +

    + If both the start_index and end_index are not passed, + then the value will be: For a Serial robot, the first "item" + returned from the serial port in response to a send. + For Dexter, it will be DMA_READ_DATA. +

    + You can use some constants for the values of start_index and end_index.
    + For a Dexter robot these are:
    + Dexter.JOB_ID
    + Dexter.INSTRUCTION_ID
    + Dexter.START_TIME //ms since jan 1, 1970 from Dexter's clock
    + Dexter.STOP_TIME
    + Dexter.INSTRUCTION_TYPE //a letter signifying the instruction type.
    +
    + Dexter.ERROR_CODE //0 means no error.
    + Dexter.RECORD_BLOCK_SIZE
    + Dexter.END_EFFECTOR_IN
    +
    + Dexter.J6_MEASURED_ANGLE
    + Dexter.J6_MEASURED_TORQUE
    + Dexter.J7_MEASURED_ANGLE
    + Dexter.J7_MEASURED_TORQUE
    + and, for J1 through J5, +
    + Dexter.J1_ANGLE
    + Dexter.J1_DELTA
    + Dexter.J1_PID_DELTA
    + Dexter.J1_A2D_SIN
    + Dexter.J1_A2D_COS
    + Dexter.J1_MEASURED_ANGLE
    + Dexter.J1_SENT
    +

    + For a Serial robot these are:
    + Serial.JOB_ID
    + Serial.INSTRUCTION_ID
    + Serial.START_TIME // ms since jan 1, 1970 from Dexter's clock
    + Serial.STOP_TIME // ms since jan 1, 1970 from Dexter's clock
    + Serial.INSTRUCTION_TYPE // A letter indicating the instruction type
    + + Serial.ERROR_CODE // means no error.
    + Serial.DATA0 // data coming back from the board
    + Serial.DATA1
    + Serial.DATA2
    + Serial.DATA3
    + Serial.DATA4
    + Serial.DATA5
    + Serial.DATA6
    + Serial.DATA7
    + Serial.DATA8
    + Serial.DATA9
    +
    + + In order to insure that the Dexter robot_status array is up to date, + you should place Dexter.get_robot_status() in your do_list before + the grab_robot_status instruction.
    + Examples of 2 adjacent instructions on a job's do_list:
    + + Dexter.get_robot_status()
    + IO.grab_robot_status("my_zero_data", Serial.DATA0)
    + grab_robot_status can be called with a subject of a job instance in place + of Robot like so:
    + Robot.dexter0.grab_robot_status("my_user_data_var") +

    + See Job Example: Serial Port for an extended example. +
    + +
    out + Very similar to the function out but made for use as a robot instruction. + Prints a message to the Output pane in the color of your choice. + This is the "print statement" as an instruction, so useful in debugging.

    + Example:
    + IO.out("hey " + 345, "green")

    + Parameters:
    + val Any JavaScript data. Most commonly this is a string, but + can be a number, an array etc.
    + color A string representing a color. Default "black". Can + be any string in the color series, or have the format:
    + "rgb(255, 100, 0)".
    + temp Default: false. With the default, printed output stays in the + Output pane until DDE is relaunched. +

    + If temp is true, + then val is still printed in the Output pane, but is over-ridden + by subsequence calls to out with temp == true. Calls to out with temp == false + will erase any "temp" output. +

    + If temp is a string, then out prints val similarly to temp == true in that + the previous call to out with that temp string will be over-ridden in place. + However, when another call to out with a different value for temp occurs, + the output of out with a temp string will not be erased. + Thus the last call to out with a given temp string remains in the Output pane, + in its place, regardless of other out calls. +

    + Unlike an instruction such as:
    + function(){out("The status is: " + this.status_code)}
    + IO.out cannot access its job instance, but it does print out + its job name and instruction ID right before printing val. +

    + See also print statements +
    +
    show_picture + This instruction takes the same arguments and has the same functionality as the method + Picture.show_picture + except that it doesn't take a callback argument.
    + Example: +
    new Job({name: "my_job",
    +         do_list: [IO.show_picture()]})
    +      
    +
    +
    show_video + This instruction takes the same arguments and has the same functionality as the method + Picture.show_video + except that it doesn't take a callback argument.
    + Example: +
    new Job({name: "my_job",
    +     do_list: [IO.show_video()]})
    +      
    +
    +
    take_picture + This instruction takes the same arguments and has similar functionality as the method + Picture.take_picture + except that it doesn't take a camera_id argument. + It does take a callback argument. + The next instruction is run after the callback returns, providing + the callback doesn't take too long, so keep callback computation fairly short + if your next instruction depends on the callback being completed. +

    + The callback is called with this bound to the job + that this instruction is running in.
    + If the callback is a string, it is considered to be the name of + a user_data variable that you want to set to the mat created + with take_picture. You can then use this mat in further instructions. +
    + Example: +
    new Job({name: "my_job",
    +         do_list: [IO.take_picture({callback: "my_pic"})]})
    +  
    + Job.my_job.user_data.my_pic
    + returns the mat. +
    +
    Brain Brain is a virtual robot used to manage other robots or do purely software tasks. -It has no special instructions of its own, but inherits Robot's instructions, just -like all the other robots. +It has no special instructions of its own, but can use Control, Human, IO and the +miscellaneous Robot instructions (function definitions, etc), +just like all the other robots.

    @@ -4889,7 +5105,7 @@ This instruction blocks further processing of the job's do_list instructions until all of the instructions on Dexter's instruction_queue are executed.

    -See also the instruction: Robot.grab_robot_status +See also the instruction: Control.grab_robot_status
    get_robot_status_immediately @@ -4941,7 +5157,7 @@

    If a joint angle is passed in as an array of one number, that number will be used as the "relative" degrees to add to the current joint angle. -So for instance, if the preious move_all_joints instruction +So for instance, if the previous move_all_joints instruction for j1 indicated 30, and the next move_all_joints command for j1 indicated [5], then j1 would be moved to 35 degrees. The syntax of using a number wrapped in an array @@ -5238,7 +5454,12 @@ } ]})
    See also read_file - +

    + Dexter.read_from_robot is a similar instruction, except that the default folder + for the source is "/srv/samba/share/" and not + "/srv/samba/share/dde_apps" as it is for Dexter.read_file. + Dexter.read_from_robot is maintained for backwards compatibility. + Most users are expected to use Dexter.read_file
    run_gcode @@ -5359,19 +5580,27 @@ DDE's socket interface to the Linux OS runnning on Dexter's processor board.
    Parameters:
    + file_name The name of the file to be written on Dexter. + Example: "/srv/samba/share/AdcCenters.txt" is + where calibration data is written.
    content A string. Default: "" The content to be written. This function automatically escapes null, semicolon and percent (the escape character) in a_string for transmission.
    - file_name The name of the file to be written on Dexter. - Example: "/srv/samba/share/AdcCenters.txt" is - where calibration data is written.
    Example:
    Dexter.write_file("/srv/samba/share/greeting.txt", "hello world")
    This instruction requires an upgrade to Dexter's software available May, 2018.

    See also write_file +

    + Dexter.write_to_robot is a similar instruction, + except that the default folder + for the file_name is "/srv/samba/share/" and not + "/srv/samba/share/dde_apps" as it is for Dexter.write_file. + Also write_to_robot has its content argument 1st, and its file_name argument 2nd. + Dexter.write_to_robot is maintained for backwards compatibility. + Most users are expected to use Dexter.write_file
    @@ -5430,15 +5659,34 @@
    RobotStatus + When an instruction is sent to Dexter from DDE, Dexter sends back to DDE + an array of 60 numbers containing robot status information. The DDE menu + item Jobs/Show robot status presents this array in a chart labeling + each number so you can make sense of it. +

    + The Robot status array is stored on each Dexter instance under, for example:
    + Dexter.dexter0.robot_status
    + You can use constants bound to index + values to extract particular values like so: + Dexter.dexter0.robot_status[Dexter.J1_MEASURED_ANGLE]
    + See the full list of such constants in the documentation for + Control.grab_robot_status +

    RobotStatus is a class whose instances store information about - a Dexter robot. Each Dexter robot instance has a field named robot_status - i.e. Robot.dexter0.robot_status + a Dexter robot. Each Dexter robot instance has a field named rs + i.e. Dexter.dexter0.rs whose value is an instance of RobotStatus.

    - The following are instance methods on RobotStatus. + The following are instance methods on RobotStatus that make it easy + to access various robot status values.
    + Examples:
    + Dexter.dexter0.rs.job_id()
    +
    new Job({name: "my_job",
    +         do_list:[function(){out(this.robot.rs.measured_angle(1))}]
    +         })     
    job_id Returns the job_id of the job corresponding to the instruction of this robot_status. - This is usually the last instruciton sent to Dexter.
    + This is usually the last instruction sent to Dexter.
    Takes no parameters.
    instruction_id @@ -5534,9 +5782,6 @@ joint_count Default: 5 An integer from 0 through 7 indicating the number of joints to return their angles on.
    - - -
    measured_angle Returns the a2d_cos of a joint.
    Parameter
    @@ -5809,7 +6054,7 @@ Human.speak({speak_data: "hi there folks", rate: 0.25, wait: true}), - Robot.out("done") + IO.out("done") ]})
    This job doesn't print "done" until after "hi there folks" has been spoken.
    @@ -5844,7 +6089,7 @@ Examples:
     new Job({name: "my_job",
         do_list: [Human.task({task: "Load filament now."}),
    -              Robot.out("We're done.")]})
    +              IO.out("We're done.")]})
                   
     new Job({name: "my_job",
         do_list: [Human.task({title: "<b>Attention</b> Operator!",
    @@ -5855,7 +6100,7 @@
                               width:  300,
                               height: 120,
                               background_color: "rgb(200, 255, 200)"}),
    -    Robot.out("We're done.")]})
    +    IO.out("We're done.")]})
         
    @@ -5951,7 +6196,7 @@ ["dd", '"joe"'], ["ee", 'this.name + " is cool"'], ["ff", 'myfn.call(this)'], - ["gg", 'Robot.out("hey")', true] + ["gg", 'IO.out("hey")', true] ], show_choices_as_buttons: true, add_stop_button: false, @@ -5999,7 +6244,7 @@ user_data_variable_name: "my_file", height: 150}), function(){ - return Robot.out("You chose: " + + return IO.out("You chose: " + this.user_data.my_file) }]})
    @@ -6054,9 +6299,9 @@ this.user_data.a_number)}]})
    In the above example, the 2nd instruction couldn't be simply - Robot.out("Amount: " this.user_data.a_number)
    + IO.out("Amount: " this.user_data.a_number)
    nor even:
    - Robot.out("Amount: " Job.my_job.user_data.a_number)
    + IO.out("Amount: " Job.my_job.user_data.a_number)
    because each of those would be evaluated during the call to
    new Job(...) and thus the new job would not yet be defined, much less have the @@ -6368,7 +6613,7 @@ See Jobs menu/Insert Example/Serial Port for an extended example.
    -
    Robot.grab_robot_status +
    Control.grab_robot_status This is not a Serial instruction, but one that can be used with any robot. It captures the robot_status, or some portion of it, and saves it away in a user_data variable on the Job instance for @@ -6376,10 +6621,10 @@

    When a Serial.string_instruction is run and the device retruns a value, that string is placed in the robot_status. You can then use -Robot.grab_robot_status to extract it from the robot status +Control.grab_robot_status to extract it from the robot status and stick it in a user_data variable. -See - Robot.grab_robot_status +See + Control.grab_robot_status
    @@ -6395,7 +6640,7 @@ In other words, the serial string_instructions run in serial, not parallel.

    -You can also call Robot.grab_robot_status with a subject of +You can also call Control.grab_robot_status with a subject of a robot instance (instead of Robot) so that you can be sure to get the correct return value from a given string_instruction instruction.
    @@ -6414,7 +6659,7 @@ robot: new Brain(), //not used, but prevents the default Robot.dexter0 from being used do_list: [Robot.S1.string_instruction("y"), Robot.S1.grab_robot_status("yes_result"), - Robot.wait_until(2), + Control.wait_until(2), Robot.S1.string_instruction("n"), Robot.S1.grab_robot_status("no_result", Serial.DATA0) ]} diff --git a/doc/release_notes.html b/doc/release_notes.html index 23ecb3fe..d00b09b8 100644 --- a/doc/release_notes.html +++ b/doc/release_notes.html @@ -5,9 +5,173 @@ } .doc_details summary { font-weight: 600; } +
    v 3.4.0, Jun 27, 2019 +Higlights: read_file and write_file, both instructions and functions improved.
    + Click help: new 2nd line decribing "context".
    + New Ref Man/Job/Definition Time Vs Run Time section.
    + set_parameter improved.
    + Robot.instructions deprecated and are now divided into + Control.instructions & IO.instructions.
    + Editor improvements. +
      +
    • Removed the "ws" package from the "dependencies" list in package.json. + This was not being used. Github called it a security risk.
    • + +
    • Click help handles literal strings better.
    • + +
    • Click help improvements for string, "new" calls, keywords.
    • + +
    • New click help printed on a new line just below regular click help + that shows the "context" of what you clicked on. + If you click on the argument of a fn call, it + tells you which argument and the name of the function. + This context help is not implemented for all JS syntactic constructs, + but does supply useful help for many common situations.
    • + +
    • make_folder now works for WindowsOS too. + This fixes a bug in write_file on WindowsOS when hte dir is none-existent.
    • + +
    • Extended Ref Man for how to get the current default robot from a running job + under both Job and Robot.
    • + +
    • Major new Ref Man/Job/Definition Time Vs Run Time section + to help the user's model of how a Job operates.
    • + +
    • to_source_code_instruction_array + fixed bug when the "indent" property doesn't exist on arg.
    • + +
    • Make Instruction fixed bug in inserting a low level Job that has never been + run and we're not attempting to segment its do_list, just print its source + as is in the dialog's do_list item.
    • + +
    • Make Instruction, if you switch instructions to set_parameter, and + the previous instruction had a name field, and that previous value + for "name" isn't a valid set_parameter name, the new name is set to "Acceleration", + i.e. the default set_parameter name.
    • + +
    • Click help now works on textareas presented in the Make Instruction dialog.
    • + +
    • Editor tab has indented to 4 spaces. Now you can "unindent" 4 spaces by typing + ctrl [ (on the PC, or cmd [ (on the mac)
    • + +
    • Slight reorg of DDE's Learn JS menu: + Now "Comments" has its own submenu and its close to the top. + "Debugging, etc" submenu renamed to "Debugging" and + doesn't contain the comment menu items.
    • + +
    • Editor: when you switch to another file, and your current file has not been saved, + and the "Save" checkbox in the output pane is not checked, + You will be prompted as to whether the current file should be saved. + If the "Save" checkbox is checked, the current file will automatically + be saved as always.
    • + +
    • Big fix to Dexter.read_file for real, as well as making Simulator more + compatible with real.
    • + +
    • Ref Man/Robot/Robot Instructions/grab_robot_status + the set of robot_status index constants has been updated.
    • + +
    • Ref Man/Robot/Dexter/RobotStatus doc corrected and extended + with explanation and examples.
    • + +
    • For Dexter.read_file and Dexter.write_file, + if the path starts with a #, + don't modify it by adding "dde_apps", just leave it alone.
    • + +
    • Conversion of Dexter.set_parameter args + from DDE high level (degrees, meters) to + what is actually sent to Dexter (arcseconds, microns) + updated to match https://github.com/HaddingtonDynamics/Dexter/wiki/set-parameter-oplet + which included fixing:
      + AngularSpeed
      + AngularSpeedStartAndEnd
      + AngularAcceleration
      + all the Cartesian parameters
      + RawEncoderErrorLimits
      + RawVelocityLimits.
    • + +
    • User Guide/Configure Dexter/Verify Joint Wiring, + now refers the user to + https://github.com/HaddingtonDynamics/Dexter/wiki/Troubleshooting + for verifying jumper wires.
    • + +
    • Ref Man/Robot/Robot Instructions/get_page, and the functions + get_page & get_page_async extended with: + (Permissible options) include the HTTP methods of:
      + GET (the default)
      + POST
      + PUT
      + PATCH
      + DELETE
      HEAD
      + OPTIONS
      + plus a zillion other options for authentication, etc.
    • + +
    • Reorg of instructions. + The Robot instructions have been moved to + Control and IO classes. + Control now has:
      + "break", "go_to", "loop","label", "suspend", "unsuspend", "sync_point", "wait_until" + "include_job", "send_to_job", "sent_from_job" "start_job", "stop_job" + "debugger", "error", "if_any_errors"
      + IO now has:
      + "get_page", "grab_robot_status", "out", + "show_picture", "show_video", "take_picture"
      + So the preferred way to reference these is: + Control.loop, IO.get_page, etc.
      + Robot.loop and Robot.get_page, etc. + are deprecated but still available. + Ref Man for Robot instructions moved into new sections for Control and IO. + All examples updated.
    • + +
    • IO.show_picture, IO.show_video, IO.take_picture + are now formally documented under Ref Man/Robot/Robot Instructions/IO
    • + +
    • The Jobs examples are better formatted with insert 4 spaces for every indent.
    • + +
    • new Jobs menu item: "New Job", inserts +
      new Job({
      +    name: "my_job",
      +    do_list: [
      +        |   (vert vbar is whare cursor is left)
      +    ]
      +})
      + into the editor. If there is a select, it assumes its one or more do_list items + and wraps the new Job around the selection. + Keystroke Ctrl-j
    • + +
    • New menu item: Editor/Indent Sel(ection) + Indents the selected code, being clever about JS code. + Preserves existing number of lines, + but does a pretty good job at intending semantically. + Keystroke shift-tab
    • + +
    • With save checkbox unchecked, and new buffer that has new content in it, + attempt to switch to a new buffer, you get a dialog of cancel, save, delete. + If you choose delete, it *should* just delete the buffer, + But instead you get the confirm dialog:
      + "before editing ____ save _____ ?
    • + +
    • read_file, write_file both instructions and functions: + their default folder is dde_apps, including on Dexter, srv/samba/share/dde_apps + BUT the "r" and "W" oplets and the older instructions + Dexter.read_from_robot, Dexter.write_to_robot (deprecated) + instructions use srv/samba/share as their default folders. + Ref Man/Robot/Dexter/Dexter Instructions/read_file and write_file documentation updated.
    • + +
    • Instructions Dexter.read_file and + Dexter.read_from_robot given default value + for "destination" param of: "read_file_content".
    • + +
    • New menu on File menu "open special..." shows dialog + for editing or showing DDE and Dexter system files:
      + dde_init.js,
      dde_persistent.json,
      error.logs,
      Defaults.make_ins
    • + +
    • Slight improvement to Ref Man/IO/Sound/Music with MIDI/Synthesizers/Midi.init
    • +
    +
    v 3.3.8, Jun 5, 2019 -Highlight: Improvements to Job Engine, Dexter.set_parameter, Kin.js and +Highlights: Improvements to Job Engine, Dexter.set_parameter, Kin.js and getting dexter-specific link lengths.
    • make_folder fixed to adjust to OS file path conventions. @@ -30,7 +194,7 @@ now handled properly and the user_data.destination is filled with an integer error code, typically 1.
    • -
    • Fixed bugs in Robot.loop that caused it not to initialize correctly +
    • Fixed bugs in Control.loop that caused it not to initialize correctly when restarting a job with a loop instruction in it.
    • new testsuite file: loop_testsuite.js
    • @@ -960,7 +1124,7 @@ advancing program_counter when clicking the Go button. Doc slightly improved. -
    • Robot.if_any_errors ref man doc improved.
    • +
    • Control.if_any_errors ref man doc improved.
    @@ -1871,7 +2035,7 @@
  • Fixed Robot.break instruction to properly handle skipping past end of loop when the loop body has nested instructions.
  • -
  • Improved doc for Robot.go_to
  • +
  • Improved doc for Control.go_to
  • Improved doc for Job.insert_instruction
  • @@ -2896,7 +3060,7 @@
  • bug fixed in inserting instructions into do list that was not properly updating the hierarchical added_items_count array. - This caused some instructions to be skipped when a Robot.go_to + This caused some instructions to be skipped when a Control.go_to backwards instruction was executed.
  • @@ -2963,7 +3127,7 @@
  • Slight improvement to Generator Job Example 4b and Job Example 4c, the job definitions.
  • Jobs menu/Insert Example/ added new item: - Robot.go_to with several jobs using the Robot.go_to instruction
  • + Control.go_to with several jobs using the Control.go_to instruction
  • dde_version_equal, dde_version_less_than dde_version_more_than renamed to not start with "dde_". doc and click_help improved.
  • @@ -3329,7 +3493,7 @@
  • In doc pane, if there is no selection and no text in the find input, the colored sections are removed and the find pane is collapsed.
  • -
  • More intra-Ref-Man clickble links esp for Robot.go_to, +
  • More intra-Ref-Man clickble links esp for Control.go_to, and uman.task references.
  • Kin.is_in_reach() has been updated.
  • @@ -4168,7 +4332,7 @@
  • New Reference manual section Object System/Calling Methods
  • -
  • Ref Man documentation for Robot.go_to now has the word +
  • Ref Man documentation for Control.go_to now has the word "goto" in it so that users can "find" such a string if they type in it.
  • diff --git a/doc_code.js b/doc_code.js index a491eee2..07c9c63e 100644 --- a/doc_code.js +++ b/doc_code.js @@ -78,7 +78,7 @@ function open_doc(details_elt, record=true){ } //fn_name can be the actual fn or a string of its name or a path to the fn, or a class -//fn name might have dots in it like "Robot.go_to" +//fn name might have dots in it like "Control.go_to" function open_doc_show_fn_def(details_elt, fn_name){ if(typeof(details_elt) == "string"){ details_elt = window[details_elt] diff --git a/editor.js b/editor.js index e7c7b853..971dca5b 100644 --- a/editor.js +++ b/editor.js @@ -36,6 +36,7 @@ Editor.init_editor = function(){ lint: true, smartIndent: false, //default is true but that screws up a lot. false is suppose to //indent each line to the line above it when you hit Return + indentUnit: 4, //default is 2. Using 4 makes it same size as tab, then cmd(mac or ctrl(PC) open square will unindent by this amount. extraKeys: //undo z and select_all (a) work automaticaly with proper ctrl and cmd bindings for win and mac ((operating_system === "win") ? {"Alt-Left": Series.ts_or_replace_sel_left, @@ -44,19 +45,22 @@ Editor.init_editor = function(){ "Alt-Up": Series.ts_or_replace_sel_up, "Alt-Down": Series.ts_or_replace_sel_down, "Ctrl-E": eval_button_action, //the correct Cmd-e doesn't work + "Ctrl-J": Editor.insert_new_job, "Ctrl-O": Editor.open, "Ctrl-N": Editor.edit_new_file, "Ctrl-S": Editor.save //windows - } : + } : //Mac {"Alt-Left": Series.ts_or_replace_sel_left, "Alt-Right": Series.ts_or_replace_sel_right, "Shift-Alt-Right": Series.ts_sel_shift_right, //no non ts semantics see above for why cuttong this "Alt-Up": Series.ts_or_replace_sel_up, "Alt-Down": Series.ts_or_replace_sel_down, "Cmd-E": eval_button_action, //the correct Cmd-e doesn't work + "Cmd-J": Editor.insert_new_job, "Cmd-N": Editor.edit_new_file, "Cmd-O": Editor.open, "Cmd-S": Editor.save, //mac + }) @@ -75,6 +79,7 @@ Editor.init_editor = function(){ unfold_all_id.onclick = function(){CodeMirror.commands.unfoldAll(myCodeMirror)} select_expr_id.onclick = function(){Editor.select_expr()} select_all_id.onclick = function(){CodeMirror.commands.selectAll(myCodeMirror); myCodeMirror.focus()} + indent_selection_id.onclick = function(){CodeMirror.commands.indentAuto(myCodeMirror)} set_menu_string(select_all_id, "Select All", "a") myCodeMirror.on("mousedown", @@ -120,7 +125,12 @@ Editor.index_of_path_in_file_menu = function(path){ //returns false if path is not in menu, true if it is. path expected to be a full path, Editor.set_files_menu_to_path = function(path) { - if(!path) { path = Editor.current_file_path } + if(!path) { + if(Editor.current_file_path){ + path = Editor.current_file_path + } + else { return false } + } let i = Editor.index_of_path_in_file_menu(path) if (i === null) { return false } else { @@ -527,6 +537,72 @@ Editor.open_on_dexter_computer = function(dex_name){ setTimeout(function() {open_on_dexter_computer_file_path_id.focus()}, 100) } +handle_open_system_file = function(vals){ + if(vals.clicked_button_value == "edit dde_init.js"){ + Editor.edit_file("dde_init.js") + } + else if (vals.clicked_button_value == "show dde_persistent.json"){ + let content = read_file("dde_persistent.json") + content = replace_substrings(content, "\n", "
    ") + if(sim_actual === true) { + warning("You are getting the content of " + path + + "
    from the DDE computer because the simulate radio button " + + "
    in the Misc pane is selected." + + "
    To get the file content from Dexter," + + "
    select the 'real' radio button.") + } + out(content) + } + else if (vals.clicked_button_value.endsWith("Defaults.make_ins")){ + let path = vals.clicked_button_value.split(" ")[1] + let rob_name = path.split(":")[0] + let rob = Dexter[rob_name] + const sim_actual = Robot.get_simulate_actual(rob.simulate) + if(sim_actual === true) { + warning("You are getting the content of " + path + + "
    from the DDE computer because the simulate radio button " + + "
    in the Misc pane is selected." + + "
    To get the file content from Dexter," + + "
    select the 'real' radio button.") + } + Editor.edit_file(path) + } + else if (vals.clicked_button_value.endsWith("errors.log")){ + let path = vals.clicked_button_value.split(" ")[1] + let rob_name = path.split(":")[0] + read_file_async(path, + undefined, + function(err, content){ + if(err) { + warning("Could not get " + path + "
    Error: " + err) + } + else { + content = replace_substrings(content, "\n", "
    ") + out("" + rob_name + ":/srv/samba/share/errors.log content:
    " + content) + } + }) + } +} + +Editor.open_system_file = function(){ + let cont = "
    on DDE Computer\n" + + "
    " + + "" + + "
    " + for(let dex_name of Dexter.all_names){ + cont += "
    on " + dex_name + "\n" + + "
    " + + "" + + "
    " + } + show_window({title: "Open System File", + content: cont, + width: 275, + height: 450, + callback: handle_open_system_file + }) +} + Editor.remove = function(path_to_remove=Editor.current_file_path){ if(path_to_remove == "new buffer") { let index = Editor.index_of_path_in_file_menu("new buffer") //returns null if none @@ -535,7 +611,7 @@ Editor.remove = function(path_to_remove=Editor.current_file_path){ if (Editor.current_file_path == path_to_remove){ let files_menu_path = file_name_id.childNodes[0].innerHTML let path = Editor.files_menu_path_to_path(files_menu_path) - Editor.current_file_path = path //if I don't do this the next call to edit_file will think we're on new buffer and pop up the 3 choices dialog again. + Editor.current_file_path = false //path //if I don't do this the next call to edit_file will think we're on new buffer and pop up the 3 choices dialog again. Editor.edit_file(path) } } @@ -653,6 +729,14 @@ Editor.edit_file = function(path){ //path could be "new buffer" //so don't do the save in that condition. Editor.save_current_file() } + else if(!persistent_get("save_on_eval")){ + let cur_content = Editor.get_javascript() + let prev_content = read_file(Editor.current_file_path) + if(cur_content != prev_content) { + let save_it = confirm("Before editing:\n" + path + "\nSave:\n" + Editor.current_file_path + " ?") + if(save_it) { Editor.save_current_file() } + } + } } if (path == "new buffer"){ Editor.edit_file_aux(path, "") } else { @@ -817,6 +901,11 @@ Editor.save_to_dexter_as = function(){ } } +//on Jobs menu/insert_new_job +Editor.insert_new_job = function(){ + Editor.wrap_around_selection('new Job({\n name: "my_job",\n do_list: [\n ', + '\n ]\n})\n') +} //replace selected text with new text. Editor.insert = function(text, insertion_pos="replace_selection", select_new_text=false){ //insertion_pos defaults to the current editor selection start. @@ -1206,12 +1295,20 @@ Editor.select_call = function(full_src = Editor.get_javascript(), cursor_pos = E var fwd_nearest_delim_pos = Editor.find_forward_delimiter(full_src, cursor_pos) var delim_char = full_src[fwd_nearest_delim_pos] if (Editor.is_close_delimiter(delim_char)){ + if((delim_char == "}") && (full_src[fwd_nearest_delim_pos + 1] == ")")) { //looks like the end of a keyword call, ie }) + //so advance the delim char to the close paren + delim_char = ")" + fwd_nearest_delim_pos += 1 + } start_and_end = Editor.find_call_start_end_from_end(full_src, fwd_nearest_delim_pos + 1) } else { var bwd_nearest_delim_pos = Editor.find_backward_delimiter(full_src, cursor_pos) var delim_char = full_src[bwd_nearest_delim_pos] if (Editor.is_open_delimiter(delim_char)){ + if((delim_char == "{") && (full_src[bwd_nearest_delim_pos - 1] == "(")) { //looks like keyword call + bwd_nearest_delim_pos -= 1 + } var close_delim = Editor.find_matching_delimiter(full_src, bwd_nearest_delim_pos) if (close_delim == null) {return false} else { start_and_end = Editor.find_call_start_end_from_end(full_src, close_delim + 1)} @@ -1358,10 +1455,19 @@ Editor.find_call_start_end_from_end = function(full_src, cursor_pos){ var last_char_before_whitespace_pos = Editor.backup_over_whitespace(full_src, temp_cur - 1) if (last_char_before_whitespace_pos && (last_char_before_whitespace_pos > 0)){ - var identifier_bounds = Editor.bounds_of_identifier(full_src, last_char_before_whitespace_pos) - var id = full_src.substring(identifier_bounds[0], identifier_bounds[1]) - if (["else", "try"].indexOf(id) != -1){ - return [identifier_bounds[0], cursor_pos] + let identifier_bounds = Editor.bounds_of_identifier(full_src, last_char_before_whitespace_pos) + if(identifier_bounds){ + let id = full_src.substring(identifier_bounds[0], identifier_bounds[1]) + if (["else", "try"].indexOf(id) != -1){ + return [identifier_bounds[0], cursor_pos] + } + } + else { + let last_char_before_whitespace = full_src[last_char_before_whitespace_pos] + if(last_char_before_whitespace == "(") { //looks like we got the ({ of a keyword fn call + let end_paren_pos = Editor.find_matching_close(full_src, last_char_before_whitespace_pos) + return Editor.find_call_start_end_from_end(full_src, end_paren_pos + 1) + } } } } @@ -1537,6 +1643,15 @@ Editor.find_backward_delimiter = function(full_src, cursor_pos){ return result } +//returns non-neg integer or null +Editor.find_backward_open_delimiter = function(full_src, cursor_pos){ + full_src = full_src.substring(0, cursor_pos) + full_src = reverse_string(full_src) + let result = Editor.find_forward_open_delimiter(full_src, 0) //could be null + if(typeof(result) == "number") { result = full_src.length - result - 1 } + return result +} + //does not bypass // comments. Editor.find_forward_open_delimiter = function(full_src, cursor_pos=0){ @@ -1635,7 +1750,7 @@ Editor.find_backwards_string = function(full_src, cursor_pos, string_to_find){ //finds starting pos of occurance of string_to_find that's close to, but before cursor_pos. //skips over // comments -//returns -1 if can't find string_to_find +//returns null if can't find string_to_find Editor.find_backwards = function(full_src, cursor_pos, string_to_find){ var pos = full_src.lastIndexOf(string_to_find, cursor_pos) if (pos == -1) { return null } @@ -2122,21 +2237,201 @@ Editor.variable_info = function(identifier){ return null } -Editor.show_identifier_info = function(full_src=null, pos=null){ +//this is the potential 2nd line output for click_help saying what arg in what fn +//was clicked on. +//returns a string or undefined meaning no help. +Editor.context_help = function(full_src, cursor_pos, identifier){ + if(identifier == "new"){ + let next_identifier_pos = Editor.skip_forward_over_whitespace(full_src, cursor_pos + 3) + let next_identifier_bounds = Editor.bounds_of_identifier(full_src, next_identifier_pos) + if(next_identifier_bounds) { + let next_identifier = full_src.substring(next_identifier_bounds[0], next_identifier_bounds[1]) + let fn = value_of_path(next_identifier) + let next_identifier_html + if(fn) { + next_identifier_html = Editor.get_atag_for_fn_name(next_identifier, full_src, cursor_pos) + } + else { next_identifier_html = "" + next_identifier + + ", which is undefined." + } + return "new " + next_identifier + + " makes an instance of the class " + next_identifier_html + "" + } + else { return } //can't find any context help + } + else { + let [fn_name, arg_index] = Editor.find_arg_index(full_src, cursor_pos) + if(fn_name){ + let result = "This is the " // "Context: " + let prefix + if(typeof(arg_index) == "number"){ + result += ordinal_string(arg_index + 1) + } + else { result += "keyword" } + result += " argument" + let fn + if(fn_name.startsWith("new ")){ + fn = value_of_path(fn_name.substring(4).trim()) + } + else { + fn = value_of_path(fn_name) + } + if(fn){ + let lit_obj = function_param_names_and_defaults_lit_obj(fn) + if(lit_obj){ + let fn_param_names = Object.keys(lit_obj) + let param_name = ((typeof(arg_index) == "number") ? + fn_param_names[arg_index] : + arg_index) + result += " (name: " + '"' + param_name + '"' + let default_val_src = lit_obj[param_name] + if(default_val_src) { + result += ", default_value: " + default_val_src + } + else {result += ", default_value: undefined"} + result += ")" + } + } + let outer_type = "" + if(fn_name.startsWith("new ")) { outer_type = "constructor: "} + else if(fn_name.includes(".")) { outer_type = "method: "} + else if(fn_name) { outer_type = "function: "} + let suffix = "" + if(!fn) { suffix = ", which is undefined" } + let fn_name_html + let fn_name_prefix = "" + /* this screws up for "Job" and "Dexter" because get_atag_for_fn_name returns + their full args, not just a link to "Job" or "Dexter". so + for now, just print out Job and Dexter in plain text. + if(fn_name.startsWith("new ")){ + fn_name_prefix = "new " + let short_fn_name = fn_name.substring(4).trim() + fn_name_html = Editor.get_atag_for_fn_name(short_fn_name, full_src, cursor_pos) + }*/ + fn_name_html = Editor.get_atag_for_fn_name(fn_name, full_src, cursor_pos) + return result + " to " + outer_type + fn_name_prefix + fn_name_html + suffix + "." + } + } +} + + +// return array of fn_name, and +// either the zero_based_arg_index non-neg_int, or string of the param name +Editor.find_arg_index = function(full_src, cursor_pos){ + let call_start_end = Editor.select_call(full_src, cursor_pos) + if(!call_start_end){ return [null, null] } + let open_delim_pos = Editor.find_forward_delimiter(full_src, call_start_end[0]) + if(cursor_pos < open_delim_pos){ //user clicked on fn name, not between parens + let bwd_open_pos = Editor.find_backward_open_delimiter(full_src, call_start_end[0]) + if((bwd_open_pos == null) || (bwd_open_pos < 0)) { return [null, null] } + else { + call_start_end = Editor.select_call(full_src, bwd_open_pos) + if(!call_start_end){ return [null, null] } + else if (call_start_end[1] < cursor_pos) { //the previous call ends before we get to cursor_pos, so we aren't in an OBVIOUS fn call, but neees work to fund unobvious ones + return [null, null] + } + else { + open_delim_pos = Editor.find_forward_delimiter(full_src, call_start_end[0]) + } + } + } + let fn_name = full_src.substring(call_start_end[0], open_delim_pos).trim() + let char_after_open_paren = full_src[open_delim_pos + 1] + if(char_after_open_paren == "{") { //we've got a keyword call ie foo({bar: 12, baz:34}) + let colon_pos + let identifier_at_cursor_bounds = Editor.bounds_of_identifier(full_src, cursor_pos) + if(identifier_at_cursor_bounds && (full_src[identifier_at_cursor_bounds[1]] == ":")) { + //user clicked IN a keyword + colon_pos = identifier_at_cursor_bounds[1] + 1 + } + else { colon_pos = Editor.find_backwards(full_src, cursor_pos, ":") } + if(colon_pos){ + let arg_name_bounds = Editor.bounds_of_identifier(full_src, colon_pos - 2) + if(arg_name_bounds) { + let arg_name = full_src.substring(arg_name_bounds[0], arg_name_bounds[1]) + return [fn_name, arg_name] + } + } + else { return [null, null] } + } + else if(cursor_pos < open_delim_pos){ //user clicked on fn name, not between parens, so not on an arg + return [fn_name, null] + } + else { + let comma_pos = open_delim_pos + for(let i = 0; i < 1000; i++) { + comma_pos = Editor.find_forward_comma_at_level(full_src, comma_pos + 1) + if(!comma_pos) { + if(cursor_pos <= call_start_end[1]) { return [fn_name, i] } + else { return [null, null] } //shouldn't if full src is correct sytnax, but just in case it isn't + } + else if(comma_pos > cursor_pos) { return [fn_name, i] } + } + } + return [null, null] +} + +//search forwards from pos for a comma, but ignore +//commas inside of nested fn calls. +Editor.find_forward_comma_at_level = function(full_src, cursor_pos){ + for(let i = cursor_pos; i < full_src.length; i++){ + let char = full_src[i] + if(char == ",") { return i } + else if("[({".includes(char)) { + let close_pos = Editor.find_matching_close(full_src, i) + if(close_pos) { i = close_pos } + else { return null } + } + } + return null +} + +//return fn_name, or an atag wrapper arond fn_name to click on for more help +Editor.get_atag_for_fn_name = function(fn_name, full_src, cursor_pos) { + let html_string = Js_info.get_info_string(fn_name, undefined, full_src, cursor_pos) + let pos_of_fn_name = html_string.indexOf(fn_name) + if (pos_of_fn_name == -1) { return fn_name } //no atag info so just return fn_name + else { + let atag_start = Editor.find_backwards(html_string, html_string.length, "") + if (atag_end == -1) { //shouldnt but just in case + return fn_name + } + else { + let tag_str = html_string.substring(atag_start, atag_end + 4) + return tag_str + } + } + } +} +//for click help on textarea's and input type ins. Used in Make Instruction textareas. +Editor.show_identifier_info_for_type_in = function(event){ + let full_src = event.target.value + let pos = event.target.selectionStart + Editor.show_identifier_info(full_src, pos) +} + +Editor.show_identifier_info = function(full_src=Editor.get_javascript(), pos=Editor.selection_start()){ var identifier = Editor.identifier_or_operator(full_src, pos) if (identifier){ - var info = Js_info.get_info_string(identifier) + var info = Js_info.get_info_string(identifier, undefined, full_src, pos) /* this is now implemented in identifier_or_operator that returns a potential array with var, let and param into in it. if (!info){ info = Editor.variable_info(identifier) if (info){ info = "" + identifier + " is " + info } }*/ if (info){ + let context_info = Editor.context_help(full_src, pos, identifier) + if(context_info) + info = info + "
    " + context_info out(info, null, true) } } } + var {persistent_initialize, persistent_get, persistent_set, load_files, file_exists, write_file, dde_init_dot_js_initialize} = require('./core/storage.js') var {warning, decode_quotes, is_alphanumeric, is_digit, is_letter, is_letter_or_underscore, is_whitespace, reverse_string} = require("./core/utils.js") diff --git a/examples/FindHome.js b/examples/FindHome.js index 03a5e9ba..a53d6776 100644 --- a/examples/FindHome.js +++ b/examples/FindHome.js @@ -277,7 +277,7 @@ function handleWindowUI(vals){ //vals contains name-value pairs for each // persistent_get(vals.macro_name,function(val){db_fetch = val}) /*Job.j1.user_data.choicemade = function () { var rt = [] - rt.push(Robot.wait_until(function(){return db_fetch})) + rt.push(Control.wait_until(function(){return db_fetch})) rt.push(function(){return replayPointsitr(db_fetch,1)}) return rt }*/ diff --git a/examples/opencv_locate_object.js b/examples/opencv_locate_object.js index d24e2874..f1649017 100644 --- a/examples/opencv_locate_object.js +++ b/examples/opencv_locate_object.js @@ -3,7 +3,7 @@ This demo is an incomplete work-in-progress. If you want to use a different camera than your computer's built in default, 1. Eval Picture.show_video_cameras() 2. Copy the ID of the camera you want to use. -3. Paste it in place of "webcam" in the below call to Robot.show_video +3. Paste it in place of "webcam" in the below call to IO.show_video The pasted ID should be wrapped in quotes. */ function locate_obj(){ @@ -31,7 +31,7 @@ function pick_up_obj(){ out("moving dexter to: " + dex_x + ", " + dex_y) return [Dexter.move_to([dex_x, dex_y, this.user_data.take_pic_point[2]]), //move above obj Dexter.move_to([dex_x, dex_y, this.user_data.dexter_down_z]), //move down to obj - Robot.wait_until(1), //todo grab obj + Control.wait_until(1), //todo grab obj Dexter.move_to([dex_x, dex_y, this.user_data.take_pic_point[2]]), //move up Dexter.move_to([0.5, dex_y, this.user_data.take_pic_point[2]]) //move away //todo drop object @@ -42,16 +42,16 @@ new Job({name: "loc_obj", user_data: {dex_y_offset: 0.15, take_pic_point: [0, (0.747 / 2) + 0.15, 0.3], dexter_down_z: 0.05}, - do_list: [Robot.show_video({content: "webcam", x: 600, y: 60}), + do_list: [IO.show_video({content: "webcam", x: 600, y: 60}), function() {return Dexter.move_to(this.user_data.take_pic_point)}, Human.task({title: "Clear the Scene", task: 'Take a "background" picture.', x: 250, y: 60, height: 120, width: 330}), - Robot.take_picture({callback: "background_pic"}), + IO.take_picture({callback: "background_pic"}), Human.task({title: "Set the Scene", task: "Place an object in view of the camera
    and take a foreground picture.", x: 250, y: 60, height: 140, width: 330}), - Robot.take_picture({callback: "foreground_pic"}), + IO.take_picture({callback: "foreground_pic"}), locate_obj, function(){inspect(this.user_data.loc_obj)}, pick_up_obj diff --git a/examples/opencv_picture_similarity.js b/examples/opencv_picture_similarity.js index 160928c6..9e50c3c3 100644 --- a/examples/opencv_picture_similarity.js +++ b/examples/opencv_picture_similarity.js @@ -3,22 +3,22 @@ Take two pictures and get their similarity score. If you want to use a different camera than your computer's built in default, 1. Eval Picture.show_video_cameras() 2. Copy the ID of the camera you want to use. -3. Paste it in place of "webcam" in the below call to Robot.show_video +3. Paste it in place of "webcam" in the below call to IO.show_video The pasted ID should be wrapped in quotes. */ new Job({name: "pic_sim", user_data: {take_pic_point: [0, (0.747 / 2) + 0.15, 0.3]}, do_list: [ - Robot.show_video({content: "webcam", x: 600, y: 60}), + IO.show_video({content: "webcam", x: 600, y: 60}), function() {return Dexter.move_to(this.user_data.take_pic_point)}, Human.task({title: "Take First Picture", task: 'Click continue to snap the 1st picture.', x: 250, y: 60, height: 120, width: 330}), - Robot.take_picture({callback: "background_pic"}), + IO.take_picture({callback: "background_pic"}), Human.task({title: "Take Second Picture", task: "Click continue to snap the 2nd picture.", x: 250, y: 60, height: 140, width: 330}), - Robot.take_picture({callback: "foreground_pic"}), + IO.take_picture({callback: "foreground_pic"}), function() { let sim = Picture.mats_similarity_by_color( //mats_similarity_by_average_color, mats_similarity_by_detect_blobs {mat_in1: this.user_data.background_pic, diff --git a/index.html b/index.html index ced639be..69796843 100644 --- a/index.html +++ b/index.html @@ -207,6 +207,7 @@