// File Transfer to Arduino. // This program transfers files to/from an Arduino with an // attached SD card, which must be formatted. // There is an accompaning Arduino sketch "File_Transfer.ino" // which must be loaded and running, and the Arduino must be // connected to the PC with a USB cable. // Requires QuickCalc BASIC 2.5 // Maximum path of 85 (for dirs) + / + 8.3 (filename) = 98 // Uses FAT16 naming convention // Note - transfer is slow. // Arduino SD file access is one byte ata a time. // It is an example of and illustrates the use of the // following QuickCalc BASIC features: // Serial Files // Binary Files // The "Select" function // MessageBox // BROWSEINPUTFILE$ and BROWSEOUTPUTFILE$ // BROWSECOMMPORTS$ // User-defined functions // INPUTDIALOG // SORT dim name_list$ (100) // increase if more than 100 files in a directory. //port$ = "COM4" // if comm port is known and fixed port$ = "" // browse for port at run speed = 19200 // must match Arduino sketch //REC_SIZE = 65 // must match Arduino sketch REC_SIZE = 127 // must match Ardiuno sketch // REC_SIZE is smaller so Nano can have // small buffers. // For Mega2560, REC_SIZE can be 127 last_path$ = workingdir$ save_workingdir$ = workingdir$ dim dialog_array$ (1,2) dim cmds$ (7) cmds$ (0) = "upload" cmds$ (1) = "delete" cmds$ (2) = "download" cmds$ (3) = "list dir" cmds$ (4) = "make dir" cmds$ (5) = "delete dir" cmds$ (6) = "exit" // debugging = 1 // (set to 1 for debug messages) //debug trace if (debugging) then print "start" if port$ = "" then port$ = browsecommports$ ("Comm port for Arduino:") open serial, 4, port$, 255, speed, "NO", 1 delay 100 900 print cmd = SELECT ("Command", cmds$, 7) // ****** if cmd = -1 then goto 995 // cancelled... exit command$ = cmds$ (cmd) if debugging then print "command$ = ", command$ if command$ = "exit" then goto 995 if cmd = 0 then nrc = upload () if cmd = 1 then nrc = delete_a_file () if cmd = 2 then nrc = download () if cmd = 3 then nrc = list_directory () if cmd = 4 then nrc = make_directory () if cmd = 5 then nrc = delete_directory () if (debugging) then print "return code = ";nrc 990 goto 900 // get another command 995 // exit print "EXITING" close 4 end //------------------------ UPLOAD ------------------------ def upload () = gosub 1000 1000 // outer subr to make sure input file got closed rc = upload_2 () close 2 // in case it is still open return rc def upload_2 () = gosub 1005 1005 if debugging = 1 then print "upload file" // First get the source file to upload chdir last_path$ source_full_path$ = BROWSEINPUTFILE$ ("Source file to upload:") last_path$ = strip_filename_from_path (source_full_path$) chdir save_workingdir$ urc = OPEN ("input binary", 2, source_full_path$, REC_SIZE) if (urc = 0) then goto 1010 beep print "Error opening input file" return -1 1010 // we close the file in the outer subr. 1020 // Now get the destination path/file on the Arduino urc = choose_arduino (2) if (urc <> 1) then return -1 // error or cancel 1030 dest_file_path$ = directory$ + new_name$ // Before we upload it, see if it already exists and if so, // do we want to overwrite it. urc = does_file_exist (dest_file_path$) if debugging then print "Return code from does_file_exist: ";nrc if (urc <> 1) then goto 1040 ulrc = MESSAGEBOX ("Do you want to overwrite this file? \n" + dest_file_path$, "cancel", "retry", "OK") if (ulrc <> 1) then return -1 // cancel if (ulrc = 2) then goto 1020 // retry - pick another name goto 1050 // OK 1040 if (ulrc = 2) then if (urc <> 2) then goto 1050 mdrc = MESSAGEBOX ("Directory exists with the same name.", "retry", "cancel") if (mdrc <> 1) then goto 1020 // retry - pick another name return -1 // cancel 1050 if (urc = -1) then return -1 // [x] cancel from dialog print "Uploading file: "; source_full_path$ print "to: "; dest_file_path$ response_to_command$ = send_command_get_response ("upload") if (response_to_command$ <> "OK") then return -1 // We should have DOING_UPLOAD, state 0 file_size = filesize (2) print "filesize = ", file_size // send a message to the Arduino with the FILE NAME (or path) file_open_message$ = send_command_get_response (dest_file_path$) // Arduino should delete and open the file // Arduino should respond with OK. if (file_open_message$ <> "OK") then return -1 // We should have DOING_UPLOAD, state 1 // Upload the file size file_size_message$ = send_command_get_response (str$(file_size)) // Arduino should delete and open the file ??? // Arduino should respond with OK. if (file_size_message$ <> "OK") then return -1 // error 1080 // We should have DOING_UPLOAD, state 2 nrc = starttimer end_of_file = 0 start_of_file = 1 retry_count = 0 //--------- (UPLOAD LOOP) -------------------------------------- 1100 // If previous transmission was an error, we will re-transmit. if retry_count = 0 then goto 1400 // read next record if retry_count < 10 then goto 1300 // too many errors. Cancel. foo$ = send_command_get_response ("-1") // Arduino should cancel, close file, and respond with OK print "Too many Errors" return -1 1300 // retry the message goto 1410 1400 line input #2, Line_read_from_file$ // read line from file if debugging then ?? Line_read_from_file$ // print original line msg_len = len (Line_read_from_file$) if msg_len = 0 goto 1500 // Calculate Hash Total gosub 11000 if debugging then ?? ht 1410 // Arduino should be in DOING_UPLOAD, state 2 // we need to upload the hash total ht$ = str$(ht, "MAX") hash_total_response$ = send_command_get_response (ht$) // Arduino should save the hash total, and respond with OK if (hash_total_response$ <> "OK") then return -1 1420 // Now we upload the translated line line_sent_response$ = send_command_get_response (Line_read_from_file$) // Arduino will respond with an error message or "OK" if line_sent_response$ = "OK" then goto 1460 if line_sent_response$ <> "Hash Total Error" then goto 1440 // we will retry the record if debugging then print "Retrying ... " retry_count = retry_count + 1 goto 1100 // loop 1440 // Some other error ?? line_sent_response$ print "Cancelling" return -1 1460 // Record was received and written to the file start_of_file = 0 retry_count = 0 if debugging then print goto 1100 // loop to get next record 1500 // we are at end-of-file. hash_total_response$ = send_command_get_response ("0") // response will always be OK eof_response$ = send_command_get_response (Line_read_from_file$) // Arduino should have closed the file and responded OK usec = endtimer print "time: ", usec/1000000, " sec" if eof_response$ <> "OK" then goto 1580 print "File Copied Successfully" return 1 1580 ?? eof_response$ print "File Error - not copied." return -1 // --------------------------- DEL ------------- def delete_a_file () = gosub 4000 4000 // Delete a file // "del" - delete a file // We must generate the complete path, e.g., A/B/C // where C is the file to delete. // returns 1 = success // -1 = error or cancel if debugging then print "Delete File" 4020 nrc = choose_arduino (1) if (nrc = -1) then return -1 full_path$ = directory$ + new_name$ // We know it is a file, // since "Choose (1) will not return a directory drc = messagebox ("Are you sure you want to delete this file? \n" + full_path$, "Cancel", "Search Again", "OK") if (drc = 2) then goto 4020 // search again if (drc = 3) then goto 4050 // OK return -1 // cancel 4050 print "Deleting: "; full_path$ response_to_command$ = send_msg_get_response ("del") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 4090 ?? response_to_command$ return -1 // error 4090 // send a message to the Arduino with the full path file_del_message$ = send_msg_get_response (full_path$) // print #4, full_path$ // Arduino should delete the file // gosub 12000 // wait for Serial Input to be ready // line input #4, file_del_message$ // Arduino should respond with OK. if (file_del_message$ = "OK") then goto 4100 if debugging then ?? file_del_message$ return -1 4100 // Success deleting return 1 // success //-------------------------- DOWNLOAD --------------- def download () = gosub 5000 5000 // outer subr to make sure output file got closed rc = download_2 () close 3 // in case it is still open return rc def download_2 () = gosub 5005 5005 if debugging then print "Download File" // First get the source file on the Arduino drc = choose_arduino (1) if (drc <> 1) then return -1 // error or cancel source_file_path$ = directory$ + new_name$ if debugging then ?? source_file_path$ // Now get the destination file on the Hard Drive 5010 chdir last_path$ dest_file_path$ = BROWSEOUTPUTFILE$ ("Select Destination File:") last_path$ = strip_filename_from_path (dest_file_path$) chdir save_workingdir$ // See if the file already exists - try to open it drc = OPEN ("input", 3, dest_file_path$) close 3 if (drc < 0) then goto 5020 // file not found (open failed) drc = MESSAGEBOX ("File " + dest_file_path$ + "\n already exists.", "Overwrite", "Re-Select", "Cancel") if (drc = 1) then goto 5020 // overwrite if (drc = 2) then goto 5010 // re-select return -1 // cancel 5020 print "Downloading: ";source_file_path$ print "to: ";dest_file_path$ response_to_command$ = send_command_get_response ("download") if (response_to_command$ <> "OK") then return -1 // We should have DOING_DOWNLOAD, state 0 drc = starttimer // send a message to the Arduino with the full file path file_open_message$ = send_msg_get_response (source_file_path$) // Arduino should open the file and respond with file size. if debugging then ?? file_open_message$ // = file size arduino_file_size = val (file_open_message$) if debugging then ?? arduino_file_size if arduino_file_size >= 0 then goto 5100 print "File Does Not Exist" // should not occur return -1 5100 drc = OPEN ("output binary", 3, dest_file_path$, REC_SIZE) if (drc = 0) then goto 5110 print "Could not open output file " + dest_file_path$ foo$ = send_msg_get_response ("cancel") // cancel the download return -1 5110 // Respond OK so Arduino will begin to send file print #4, "OK" retry_count = 0 5130 // Now Arduino is in state 2 gosub 12000 // wait for Serial Input to be ready line input #4, hash_total_message$ if debugging then ?? hash_total_message$ arduino_hash_total = val (hash_total_message$) // respond OK to hash total message print #4, "OK" // Now Arduino goes to state 3 gosub 12000 // wait for Serial Input to be ready line input #4, Line_read_from_file$ if debugging then ?? line_read_from_file$ msg_len = len (Line_read_from_file$) // Check for end-of-file if msg_len = 0 then goto 5300 // Calculate hash total on received line gosub 11000 if debugging then print "Calculated hash total: ", ht // compare hash totals if (arduino_hash_total = ht) then goto 5200 // hash total (checksum) error print "checksum error" retry_count = retry_count + 1 if (retry_count < 10) then goto 5150 // too many retries. Cancel. print "Failed after 10 retries. Cancelling." close 3 download_cancel$ = send_msg_get_response ("cancel") if debugging then ?? download_cancel$ return -1 5150 // we are going to retry the transmission print #4, "retry" goto 5130 5200 // Checksums compare OK retry_count = 0 // Write translated message to file print #3, Line_read_from_file$ print #4, "OK" goto 5130 5300 // zero-length record = end-of-file // no further interaction with Arduino. // Arduino goes to IDLE close 3 // received file is complete. // verify size open input, 3, dest_file_path$ // re-open file file_size = filesize (3) if debugging then print "new file size = ", file_size close 3 if (arduino_file_size = file_size) goto 5400 print "Total file size does not compare." beep return -1 // We should probably delete the bad file. 5400 print "Download Successful." usec = endtimer print "time: ", usec/1000000, " sec" return 1 //---------------------------- DIR ------------------- def list_directory () = gosub 6000 6000 // "dir" if debugging then print "List Directory" first_file = 0 directory$ = "/" nrc = choose_arduino (3) if (nrc = -1) then return -1 print "Directory list for: "; directory$ response_to_command$ = send_msg_get_response ("dir") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 6090 ?? response_to_command$ return -1 // error 6090 if debugging then ?? directory$ print #4, directory$ // give Arduino the path of the directory 6100 gosub 12000 line input #4, line$ if line$ = "bad path" then ? "bad path": goto 990 if line$ = "done!" then goto 6200 first_file = 1 if right$(line$, 1) = "/" then goto 6150 // directory input %line$, filename$, filesizex print using "##,###,###"; filename$, filesizex goto 6170 6150 print line$ 6170 print #4, "OK" goto 6100 // loop reciving filenames 6200 // no more files if first_file = 0 then print "<---empty--->": return 0 print "---end of list---" return 0 //---------------------------- MKDIR ------------- def make_directory () = gosub 7000 7000 if debugging then print "mkdir" // "mkdir" - create a directory // We must generate the complete path, e.g., A/B/C // where C is the new directory. 7020 nrc = choose_arduino (4) if (nrc = -1) then return -1 full_path$ = directory$ + new_name$ ?? full_path$ // path for mkdir must be 1 shorter than Create" allows // because a / is added at the end when you try to delete it. ? len (full_path$) if len (full_path$) < 86 then goto 7030 beep rc = messagebox ("Path is too long", "OK") return -1 7030 // Before we try to create the directory, let's check if // it already exists, or conflicts with a file name. nrc = does_file_exist (full_path$) //print "Return code from does_file_exist: ";nrc if (nrc <> 1) then goto 7070 mdrc = messagebox ("Proposed directory name conflicts with an " + "existing file name.", "retry", "cancel") if (mdrc = 1) then goto 7020 else return -1 7070 if (nrc <> 2) then goto 7080 mdrc = messagebox ("Directory already exists.", "retry", "cancel") if (mdrc = 1) then goto 7020 else return -1 7080 if (nrc = -1) then return -1 // Now neither the dir. or file with same name exists. print "Creating directory: "; full_path$ response_to_command$ = send_msg_get_response ("mkdir") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 7090 ?? response_to_command$ return -1 // error 7090 // send a message to the Arduino with the path+directory_name mkdir_message$ = send_msg_get_response (full_path$) // Arduino should create the directory and respond "OK" or "Error" print mkdir_message$ if (mkdir_message$ <> "OK") then return -1 print "mkdir successful" return 0 //---------------------------- RMDIR ------------- def delete_directory () = gosub 8000 8000 if debugging then print "Delete Directory" // "rmdir" - delete a directory // Directory must be empty or this will fail. // We must supply the complete path, e.g., A/B/C // where C is the directory to remove. 8020 nrc = choose_arduino (3) if (nrc = -1) then return -1 // Before we try to delete the directory, let's check if // it exists, and actually is a directory. rdrc = does_file_exist (directory$) //print "Return code from does_file_exist: "; rdrc if (rdrc <> 1) then goto 8070 rdrc = messagebox ("Name specified is not a directory " , "retry", "cancel") if (rdrc = 1) then goto 8020 else return -1 8070 if (rdrc <> 0) then goto 8080 rdrc = messagebox ("Directory does not exist.", "retry", "cancel") if (rdrc = 1) then goto 8020 else return -1 8080 if (rdrc = -1) then return -1 // error - user exited messagebox // Now we know the directory exists and is valid. // we must verify that the directory is empty before trying // to delete it. rdrc = is_directory_empty (directory$) // 0 = not empty, 1 = empty if (rdrc = 1) then goto 8085 rdrc = messagebox ("Directory is not empty.", "retry", "cancel") if (rdrc = 1) then goto 8020 else return -1 8085 print "Deleting directory: "; directory$ response_to_command$ = send_msg_get_response ("rmdir") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 8090 ?? response_to_command$ return -1 // error 8090 // send a message to the Arduino with the Directory name // input "Directory to Remove (full path):", remove_path$ // print #4, remove_path$ rmdir_message$ = send_msg_get_response (directory$) // Arduino should remove the directory and respond "OK" or "Error" // // gosub 12000 // wait for Serial Input to be ready // line input #4, rmdir_message$ //print rmdir_message$ if rmdir_message$ = "OK" then return 1 else return -1 //---------------------------- CHOOSE ------------------- def choose_arduino (mode) = gosub 9000 9000 // "choose" // mode = 1 select a file to download or delete // (must exist already) // 2 select a file to upload / create (new or existing) // 3 select a directory to delete or list // 4 select a directory to create // returns 1 if successful // returns -1 if error // returns 0 if user cancelled // filename selected is in new_name$ // path selected is in directory$ if debugging then print "Choose" dir_cmd$ = "dir" if (mode = 3 or mode = 4) then dir_cmd$ = "dird" // just show directories if debugging then ?? dir_cmd$ response_to_command$ = send_msg_get_response (dir_cmd$) // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 9060 ?? response_to_command$ return -1 // error 9060 first_file = 0 // ?? directory$ = "/" // start at root 9100 if debugging then ?? directory$ print #4, directory$ // give Arduino the path of the directory i = 0 // put "dummy" first entry of ".." into list. if directory$ = "/" goto 9110 // no "back 1 directory" for root name_list$ (i) = " .. " // leading space sorts first i = i + 1 9110 if mode <> 3 then goto 9120 name_list$ (i) = " [select this directory]" i = i + 1 9120 if mode <> 2 then goto 9130 name_list$ (i) = " [new file in this directory]" i = i + 1 9130 if (mode <> 4) then goto 9140 name_list$ (i) = " [new directory (here)]" i = i + 1 9140 if debugging then ?? i if debugging then ? "name_list$ (i) = ";name_list$(i) gosub 12000 line input #4, line$ if debugging then ?? line$ if left$(line$, 8) <> "bad path" then goto 9141 ? "bad path" ? line$ return -1 9141 if line$ = "done!" then goto 9200 first_file = 1 if right$ (line$, 1) = "/" then goto 9150 // directory // we have a file name // if we are just looking for a directory (to delete or create) // we will not add the file name top the list. if (mode = 3 OR mode = 4) then goto 9142 input %line$, filename$, filesizex //print %line$; using "##,###,###"; filename$, filesizex //print line$ name_list$ (i) = filename$ // put file name into array i = i + 1 9142 goto 9170 9150 // directory //print line$ length = len (line$) name_list$ (i) = "+" + mid$ (line$, 1, length-1) i = i + 1 9170 print #4, "OK" goto 9140 // loop reciving filenames 9200 // no more files if debugging then print "no more files" if first_file = 0 then print "<---empty--->": return -1 //?? 9250 // ? i if (i >= 2) then sort name_list$, count=i directory2$ = directory$ if (len (directory2$) > 28) then directory2$ = left$ (directory2$, 28) + "\n" + mid$ (directory2$, 29, 255) // split if (len (directory2$) > 55) then directory2$ = left$ (directory2$, 55) + "\n" + mid$ (directory2$, 56, 255) // split if (len (directory2$) > 82) then directory2$ = left$ (directory2$, 82) + "\n" + mid$ (directory2$, 83, 255) // split i = select ("choose file or directory\n"+directory2$, name_list$, i) // ****** if i >= 0 then goto 9300 print "user cancelled" // ?? return -1 9300 new_name$ = name_list$ (i) if left$ (new_name$, 1) <> "+" then goto 9310 new_name$ = mid$ (new_name$, 2, 100) + "/" 9310 if left$ (new_name$, 2) <> " [" then goto 9320 if (mode <> 3) then goto 9315 // [select this directory] new_name$ = "" 9315 if (mode <> 2) then goto 9318 // [new file] dialog_array$ (0,1) = "File Name" dialog_array$ (0,0) = "new_name$" rc = inputdialog ("Enter file name to upload to",dialog_array$) // *********** if rc < 0 then return -1 // (we might want to edit the file name here) goto 9330 9318 if (mode <> 4) then goto 9320 // [new directory (here)] dialog_array$ (0,1) = "Directory Name" dialog_array$ (0,0) = "new_name$" rc = inputdialog ("Enter new directory to create", dialog_array$) // *********** if rc < 0 then return -1 // (we might want to edit the directory name here) goto 9330 9320 // If the user selected a directory then we will // search that directory. The selected directory // name is added to the current path. if new_name$ <> " .. " goto 9330 //print "backup button selected" 9325 j = len (directory$) if right$ (directory$, 1) = "/" then j = j - 1 while j > 0 if mid$ (directory$, j, 1) = "/" then break j = j - 1 wend if j > 0 then directory$ = left$ (directory$, j) else directory$="/" goto 9380 9330 if right$(new_name$, 1) = "/" then goto 9350 // directory // no, was a file. if debugging then print "file selected was ", new_name$ if debugging then print "path selected was ", directory$ return 1 9350 if directory$ = "/" then directory$ = "": goto 9370 if right$ (directory$, 1) = "/" then goto 9370 directory$ = directory$ + "/" 9370 directory$ = directory$ + new_name$ if len (directory$) < 87 then goto 9380 if (mode = 1 or mode = 2) then goto 9380 //OK if selecting file beep rc = messagebox ("Path is too long", "OK") goto 9325 // backup 9380 if debugging then print "searching "; directory$ response_to_command$ = send_msg_get_response (dir_cmd$) // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 9360 print response_to_command$ return -1 9360 goto 9100 //------------------------See if file exists ---------------- def does_file_exist (file_spec$) = gosub 9500 9500 // see if file (or directory) exists // checks directory$ / new_name$ // returns: 0 = not found (does not exist) // 1 = file exists // 2 = it is an existing directory response_to_command$ = send_msg_get_response ("exists") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 9560 ?? response_to_command$ return -1 9560 // send a message to the Arduino with the FILE NAME (or path) //full_path$ = directory$ + new_name$ //print "checking for: "; file_spec$ file_exists_message$ = send_msg_get_response (file_spec$) // Arduino should respond with 1="File Exists", 0="No File" 2="Dir" //? file_exists_message$ // print response if (file_exists_message$ = "1") then return 1 if (file_exists_message$ = "0") then return 0 if (file_exists_message$ = "2") then return 2 return -1 // (error ??) //=============================================== def is_directory_empty (dir$) = gosub 9700 9700 //print "checking if empty "; dir$ response_to_command$ = send_msg_get_response ("dir") // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 9760 //?? response_to_command$ return -1 // error 9760 // send Arduino the path of the directory line$ = send_msg_get_response (dir$) if line$ = "bad path" then ? "bad path": return -1 if line$ = "done!" then return 1 // directory is empty // if we didn't get "done!" then we are still in "dir // mode and we need to cancel out of it. response_to_command$ = send_msg_get_response ("cancel") return 0 // directory is not empty //================================================= 11000 // Calculate Hash Total ht = 0 for i = 1 to msg_len c$ = mid$ (Line_read_from_file$, i, 1) a = asc (c$) m = a * i ht = ht + m // print i, c$, a, m, ht next return //--------------------------- 12000 // Wait until input message is availalble 12820 msg_avail = input (4) if (not msg_avail) goto 12820 // loop until msg avail return //while (not input (4)) //wend //--------------------------- def send_command_get_response (command$) = gosub 13000 13000 // Use this routine when the expected response is "OK" // send a message to the Arduino with the COMMAND if debugging then ?? command$ response_to_command$ = send_msg_get_response (command$) // Arduino should respond with OK. if (response_to_command$ = "OK") then goto 13080 ?? response_to_command$ // some kind of error 13080 return response_to_command$ // -------------------------------------------------- def send_msg_get_response (send_msg$) = gosub 15000 15000 // send a message to the Arduino print #4,send_msg$ // Send message to Arduino gosub 12000 // wait for Serial Input to be ready line input #4, response$ if debugging then print "response to ";send_msg$; " = "; response$ return response$ //----------------------------------------------------- def strip_filename_from_path (path$) = gosub 16000 16000 // The user has selected a path\filename from BROWSEINPUTFILE$ or // BROWSEOUTPUTFILE$. We want to know the path so the next time // we browse, we can start in that directory (saving time). pathlen = len(path$) while pathlen > 0 if (mid$(path$, pathlen, 1) = "\\") then break pathlen = pathlen - 1 wend // we are on the \ char or at the start of the string. if (pathlen = 0) then pathlen = 1 // should not occur return left$ (path$, pathlen-1)